COMPUTE! ISSUE 32 / JANUARY 1983 / PAGE 171
No gossip to start with this month. Instead, let’s start right off into a whole series of interesting tidbits (and even a few tidbytes).
Several articles have been written on how to tell whether you have a GTIA or CTIA in your system. Most of them suggest that you use a GRAPHICS 9 statement and observe the screen (it turns black with a GTIA, remains blue with a CTIA).
But suppose you want to write a program that takes advantage of all the capabilities of the GTIA. What does the poor user with only a CTIA do? If you are commercially clever, you will have your program sense which chip is in use and adapt itself accordingly. This program will enable you to do just that:
100 GRAPHICS 0:REM ALWAYS USE THIS MODE 110 PRINT "NOW TESTING FOR CTIA VERSUS GTIA" 120 PRINT "================================" 130 POKE 559,58:POKE 53277,2:REM ENABLE PLAYERS 140 POKE 54279,240:REM USE ROM FOR PLAYER DATA 145 POKE 53248,80:REM CENTERED PLAYER 150 POKE 53278,0:REM CLEAR COLLISION REGISTERS 160 POKE 623,65:REM ENABLE GTIA, IF IT EXISTS 170 POKE 20,0 180 IF PEEK(20)<2 THEN 180 190 POKE 623,1:REM DISABLE GTIA 200 POKE 559,34:POKE 53277,0:REM TURN OFF PLAYERS 210 FOR A=53261 TO 53265:POKE A,0:NEXT A:REM (AND PLAYER DATA) 220 IF PEEK(53252) THEN PRINT "SORRY, ONLY A CTIA":GOTO 240 230 PRINT "AHA ! A GTIA." 240 END
First of all, to give credit where it is due, I should mention that I was inspired to try this by a remark I read in one of Craig Chamberlain’s articles in the M.A.C.E. newsletter (Michigan Atari Computer Enthusiasts, in the Detroit area). Portions of that article were also reprinted recently in COMPUTE!.
But let’s now discuss the program. First, we must explain why and how it works: there is no way to inquire of the chip itself which it is. Even the operating system does not know which is installed. But…. (There had to be a “but,” or there wouldn’t be this article.)
There are a few subtle differences between how the two chips view players and missiles. In particular, the GTIA doesn’t believe that players can collide with “printed” characters, so it never reports such a collision. The CTIA, though, considers a character to be just another kind of COLORed (and SETCOLORed) display.
The first thing our listing does is insure that we have some mode zero characters on the screen (lines 100–120). Then we enable the player DMA and the players themselves (line 130). And we tell the chips that the player data memory is smack in the middle of the ROMs! (Why? To insure that lots of data bits will be on, forcing lots of collisions between the player and the playfield screen characters.)
With line 145, we place the player somewhere left of the center of the screen, insuring that it will collide with our printed message. Then, after clearing the collision registers (to insure that the later results will be valid), we enable the special modes of the GTIA (lines 150 and 160).
We wait for at least one full screen scan (lines 170 and 180), to be sure that the collision will “take” (if it’s going to). Then we turn everything back off again (lines 190–210).
Finally, we inspect the collision register for player zero. If a collision did occur, it must be because the older CTIA was installed. If no collision occurred, we presume that we have a GTIA.
All of this is a little complicated, but I sincerely hope that some of you game developers out there will start designing some good GTIA-based games, now that you can have them modify themselves for the CTIA owner.
In his article on “The Atari Wedge” (in the November 1982 COMPUTE!), Charles Brannon mentions that BASIC treats a line beginning with a period as a REMark, claiming that it is a lucky fluke. Well, it really isn’t a fluke. It’s just one of those things that got designed into Atari BASIC and then forgotten about.
The rule for using abbreviations in Atari BASIC (and BASIC A+, naturally) is fairly simple: when a statement begins with an abbreviation (any alphabetic characters followed by a period), BASIC searches the keyword name table for the first statement name which matches the abbreviation, starting at the first character of the abbreviation and ending at the period.
This means, for example, that “L.” will match “LIST” only because LIST is the first word in the keyword name table that begins with an “L”. If “LET” had been placed before “LIST” in this table, then “L.” would have been interpreted as a LET statement. Boy, aren’t we lucky that LIST comes before LET!
Luck had nothing to do with it. The order of those keywords was carefully chosen to provide the maximum usability of the shortest abbreviations. (Actually, I now believe that there are a few variations in the order that might be more useful; but remember that the order was set by intuition, not experience, since the language didn’t then actually exist.)
Anyway, Atari had asked for a very short abbreviation for REMark statements (e.g., “!”, as is used by most Microsoft BASICs). But what could be shorter than a single period? It’s even easier to use than “!” (no shift key needed). How to produce that result? Trivial! Place REM as the first statement name in the keyword table.
So try it sometime. Why type in three characters (“REM”) when one will do? Of course, because of the tokenizing nature of Atari BASIC, any abbreviated statement(s) are LISTed in their full form. So “.” will be LISTed as “REM”.
And a P.S. for those of you into BASIC internals: note that this implies that the token value for REM must be zero, since the token values relate directly to the order of the names in the keyword table.
I kind of promised myself that I would get down off my soap box this month and quit ranting and raving. But I couldn’t go one whole column without a little preaching, could I?
Stay out of page 6! I can’t believe it! It seems that every other article and/or utility program and/or device driver that I run across wants to place itself in page 6 (memory locations $600 to $6FF, 1536 to 1791 decimal). It won’t work!
How can I possibly install a printer driver in page 6 and then put my player vertical move routine there and my disk block input and output and …. Ah, come on, folks. Give us a break.
If you are writing a complete “system” (a game, or data base program, or whatever), then you are naturally free to configure memory as you wish, including doing whatever you want to page 6. But if you are going to publish a utility in a magazine or include a device driver with your printer interface board or do anything that others might use or modify, please don’t make it fixed-assembled in page 6. Please.
Besides, it is not true that BASIC leaves all of page 6 alone. If you do an INPUT from disk (or cassette or anything other than the screen), and if the data you input exceeds 128 bytes, BASIC will use at least a portion of page 6 as its buffer. (However, it is probably—not surely, just probably—safe to use memory from $680 to $6FF.)
A little history: If you examine your Atari BASIC reference, you will find that there are two memory usage tables. One claims that all of page 6 is available for the user. The other claims that only the upper half is available. In general, you should believe the latter. It is not a design flaw nor an error that BASIC sometimes uses the bottom half of page 6. It is necessary and documented.
I think it was someone at Atari (my rumor sources say Chris Crawford, but this is unconfirmed) who began using all of page 6 for assembly language routines. And, as I stated above, there is really nothing wrong with doing so within a “closed” environment (where you write all the software, both BASIC and assembler). Just don’t do it for public consumption.
So what should you do, instead? The best solution is to write self-relocatable code and load it wherever there is free memory (e.g., in a BASIC string). (Showing how to write self-relocatable code might be an instructive article, in and of itself. Any takers?)
The second best solution is to perform my favorite trick: place your code at LOMEM and move LOMEM up. Even here, though, it is best to use relocatable code, so you can run under a variety of operating system configurations and varying heights of LOMEM (as I documented in last month’s column).
And, last but not least, I have some good, practical (and a little bit selfish) reasons for avoiding page 6: BASIC A+ uses a good portion of it ($610 through $642, actually). Does that make us a villain? Perhaps a little, to the article writers. But we aren’t that terrible: I understand that Microsoft BASIC uses all of page 6. And who knows what other languages and operating systems and peripheral devices and whatever will also use page 6? Why complicate both your and others’ lives by putting your routines there also?
I received a very well written and thought-out letter from Steven Weston, of Del Mar, California, regarding the benchmarks I reported in my September 1982 column. Mr. Weston shares the predilections of some others, considering FORTH to have been slighted in that column (and in the following one, I presume).
First, I should like to report that he translated the BASIC benchmark to FORTH and obtained a time of a little under 118 seconds. Which is interesting, since ValFORTH (the version he used) makes use of the Atari floating point routines, I believe. So why should it be slower than Atari BASIC? If I were guessing (which means I’m about to take a flyer), I would presume that the floating point words for ValFORTH are written in FORTH words, instead of being written as low-level (assembly language) words. The very operation of stacking and unstacking the floating point numbers must then be relatively slow and painstaking.
If this is indeed true, then my comment is a positive one: the FORTH user indeed has the choice of implementing “commands” (words) either way, with other FORTH words or with assembly language. This flexibility is poorly supported by most other languages. (Although many C compiler implementations come close to having such accessible assembly code. C/65 functions, for example, need very little overhead in the assembly language code to “unstack” their parameters.) Want a faster FORTH instead of a smaller one? Recode some routines in assembly language.
Before going on to the second point of Mr. Weston’s letter, I should like to note that I feel that perhaps he (and many other readers) missed part of the point of the benchmarks: I was really trying to show how useless any one benchmark is, since it is so easy to dream up benchmarks which show off the best features of a given language. I would be hard pressed to construct even a set of ten benchmarks which would adequately compare languages.
And even if I thought I succeeded, how much is the human interface to a language worth? PILOT is still the easiest language on the Atari to learn and interface to. By definition, it therefore out-benchmarks every other language for beginners. But would anyone seriously propose using PILOT for generating prime numbers? I think not. Benchmarks are usually worth the paper they are printed on and no more.
So now to Mr. Weston’s second point. I quote: “…the bottom line on languages is to use that language which is best suited to the task. [With Atari BASIC] the lack of integer based math is a serious deficiency which can preclude its use by professional software authors.” He goes on to ask why I don’t provide a “toolbox” of integer math routines to be interfaced to Atari BASIC “instead of defending an inadequate situation.”
Well. Kudos and jibes all in one it seems. Anyway, he is absolutely right: pick the language that fits the job instead of making the job fit the language. You will remember, I hope, that in a recent column I mentioned that I collect languages like some people collect games. I keep hoping to find one that will be useful to me.
But now let me disagree a little on a couple of points. And I do so because I have received too many comments in this same vein. (1) Integer math is not needed by all “professional software authors.” The person writing a financial package needs integer math about as much as the game writer needs floating point. If you need integer math, choose a language which supports it. (2) BASIC is, unfortunately, a non-extensible language. Sure, we could put integer math routines in memory somewhere and use them from BASIC. But BASIC would still insist on thinking of its variables and constants as floating point, and the conversion time (from floating point to integer to floating point, ad nauseam) would wipe out all speed advantages gained. (3) I don’t think Atari BASIC is an “inadequate situation.” Sure, I think there are other solutions. Why else would our company produce languages such as BASIC A+ and C/65 (and probably more to come)? But “inadequate”? I think not, if it is used for and how it was meant to be used. (If anything is inadequate, it is the 6502 microprocessor, which does not lend itself to the implementation of powerful language compilers.)
But, if you are a beginner, don’t let anyone (including me) pressure you into trying to learn a new language before you are ready. It is true that you are not going to write “Super Invading Packers with Tronic Fighters” with Atari BASIC. But just look at what you can write! Ten years ago, a computer fanatic would have sacrificed his left thumb for what we now take for granted. Seven short years ago, the “hot” computer game that everybody was rewriting (to make it fit in their expanded memory 8K byte gigantic machine) was Wumpus. Today, I seldom see a published program that doesn’t make Wumpus look like something out of the dark ages. Hang in there, folks, you ain’t seen nuthin’ yet.
Well, I finally got time to take a long, hard look at the new ANSI BASIC specification. Whew! I think the tower of Babel must have seemed organized by comparison. Even ADA and PL/1 look like closely designed languages compared to ANSI BASIC. I think that the rule in designing it was “If someone wants it, let’s put it in.”
You certainly won’t see any microcomputer interpreter implementations of it in the near future. I estimate it would take over 80K bytes of Z-80 code to do it (which translates to maybe 100K to 120K of 6502 code). It is definitely designed to be compiled, not interpreted, and then only by big machines.
The error descriptions alone would take a few kilobytes (and they are required!). And what do lines like the following mean?
MAT PRINT #N, SAME, IF THERE EXIT FOR: A$; B$, C ASK #3: access outin, organization org$, rectype internal, pointer p$
I am disappointed. I had hoped that the committee would distill the best of the various BASICs and come up with a somewhat enhanced version of the original ANSI standard BASIC. Instead, they seem to have distilled out the biggest features of the biggest BASICs they can find. And who will use the standard? Not the micros. (At least not in the near future. I understand that Microsoft’s representative on the committee dropped out. From frustration? I would have.) Not those who need to contract with the government. (Soon, you will have to use ADA if you work with the defense department and various allied agencies.) Not the big business computer users. (They can’t afford to go from COBOL, a clumsy but eminently maintainable language, to a BASIC as kludged up by the committee, with a lack of the data structures that made COBOL successful.)
I guess I believed that the only BASIC users that would be left in a few years would be the hobbyists and the time-sharing companies. Now, I think the only ANSI BASIC users will be the time-sharing companies. Maybe.
As much as I disagree with much of what Microsoft has done, I would rather have seen Microsoft BASIC (version 5, on the CP/M machines) become the standard than the hodgepodge the ANSI committee has selected. ANSI, on a scale of 10, I give you a 2.
Perhaps by the time you read this, the new Atari computers will be on display at the Consumer Electronics Show (early January, in Las Vegas). Don’t expect any real surprises. I expect to hear of a 64K machine (with no software to take advantage of the extra 16K). And probably a low-end 16K machine.
Obviously, Atari needs to get in there and fight with Commodore, both on price and features. Price is easy. Features? Well, if Commodore follows through as they claim they will, it could be a tough fight. And I think the 400 replacement might outstrip the VIC-20. I guess I should note that I am not as much of an Atari loyalist as this paragraph makes me sound. It’s just that I like a good, competitive race. The consumer is bound to win.
Oh, yes, one more thing. No more right-hand cartridge slot in the new machines. And no memory board slots at all. Ouch? I don’t know. I hope there will be a good way to expand the new machines, but we will all have to wait to see what it is.
All this talk about benchmarks and ANSI BASIC has made me regain interest in a project I thought of doing a while back. So, starting next month, we will begin writing a BASIC interpreter right here in this column. And we will write it in BASIC. Interested? I am.
COMPUTE! ISSUE 33 / FEBRUARY 1983 / PAGE 190
This month we will examine the possibility of a “default” drive number under DOS. There is also a tidbit about initializing DOS disks from BASIC. Next month, we will begin what will be a three- or four-month series on how to write your own BASIC interpreter.
First, let me state that I do not recommend this section to the relative novice. While it is true that you can perform the operations I am about to describe entirely from BASIC, it is also true that you can destroy memory very nicely if you slip up. Enough warning. To begin:
Have you ever (often?) grumbled over the fact that you have to specify not only the file name, but also the disk name and drive number (e.g., “D2:MISSILE.CMD”)? I sure have. In fact, I hate it so much that when we did OS/A+ for the Apple II, we allowed the user to supply a default device specifier (e.g., “D2:”), which is automatically prefixed to all file names which do not specify a device. (Consequence: you must use a colon when you really want a device; “P” is seen as “D2:P”, though “P:” works fine.)
This concept is not new or unique; even in the micro world, such giants as CP/M use default drive assignments. Usually, the advantage of such defaults is that people with multiple disk systems need not always run a given program in a certain drive. Or the user might choose which drive will receive his data files via a simple set of keystrokes at system powerup. Suffice it to say that those who get used to default drives love them.
Unfortunately, as much as I would like to do the same thing for the Atari, I can’t. The initial device name determination under Atari’s OS is done in the OS ROMs, and Atari OS simply looks at the first letter of any file name and assumes that it is the device name.
However… (You knew there was a “however” lurking, didn’t you?) At least we could modify the File Manager System (also known as FMS, DOS, or even OS/A+) to understand the concept of a default device NUMBER. In other words, we could have the FMS inspect the file name and assume a particular drive number if “D:…” were coded. Then we could have some means of telling the FMS what the “current” drive was (and, in fact, such means already exist in OS/A+), and the system would automatically insert the correct drive number.
And yet, I am reluctant to adapt such an approach with Atari DOS. Too many programs have been written which assume that “D:…” is equivalent to “Dn:” and I am loath to introduce more confusion than is necessary. So, if you really would like to modify your copy (copies?) of FMS to allow “D:” to represent “Dn:”, let me just point you in the right direction. For this purpose, I will presume that you have a copy of Inside Atari DOS (COMPUTE! Books, 1982).
There is a routine labeled FNDCODE (File Name DeCODE) which begins on page 83 of the book and is the heart of the entire disk file name processing. Lines 4101 through 4106 start at the third character of the name and search from there backwards for the colon (‘:’) which terminates the device specifier (and ignore the comments in the listing…they are flat out irrelevant). Obviously, it would be no big deal to check to see if the character before the colon is the ‘D’ and, if so, assign a default device number.
Now, for the rest of you, I have an alternate proposal. How about changing FMS so that, if it sees a file name of “D0:…” it assigns the default device instead. I chose “D0:” because there should be no conflict with existing software. And, yet, it is a legal device specifier which is easily detectable and changeable.
Since the OS ROMs have already decoded the device number by the time FMS gets control, we don’t need to look at the file name at all. Instead, we look at the field labeled ICDNO (or, in zero page, ICDNOZ), the device number as set up by the OS ROMs. And, conveniently, FMS is already manipulating this number in a single, well-defined place, the “SETUP” routine (as listed on page 92 of Inside Atari DOS). Currently, the code sequence is simply:
LDY ICDNOZ ; move device number... STY DCBDRV ; ...to device control block
What we want instead is something like the following:
LDY ICDNOZ ; get device number... BNE OKDNO ; ...if wasn't “D0:”, it is OK LDY DEFAULT ; otherwise, change 0 to default OKDNO STY DCBDRV ; in either case, set up DCB
Now I can’t think of a much simpler change than adding two instructions, but how do we make such a change? The solution is to use what is known as a “patch.” Generally, there are two kinds of machine language patches: those that fit into the original code space and those that don’t. The former kind are easy; simply overlay the old code with the new. The latter are not so easy. Naturally, this change falls into the latter category.
With a 6502, the usual method of installing out-of-line patches is to try to replace a three-byte instruction with a JMP or JSR to the patch (failing this, you must replace two or three instructions, which may involve putting a NOP before or after the JSR or JMP). Luckily, we do indeed have a three-byte instruction that we can replace (the STY DCBDRV uses three bytes, since DCBDRV is not in zero page).
So our patch will look like this:
DCBDRV = $301 ; object of the STY *= $1176 ; address of the STY instruction JSR PATCH *= PATCH BNE OKDNO ; non-zero device number LDY DEFAULT ; replace zero device number OKDNO STY DCBDRV ; the patched-over instruction RTS
So far, so good. It makes sense, I hope. But there are two locations undefined in the above listing: we don’t know where PATCH and DEFAULT are going to be located. Again, we will refer to the book for some clues as to where they should be.
As it turns out, there is no patch space at all within the main code space of FMS. However, if we look at the very end of the listing (page 98 in the book), we find that FMS (including its internal buffers, etc.) ends at $1500. But remember that “DOS.SYS” consists of more than just FMS. In the case of OS/A+, DOS also includes “CP,” the console processor, and actually ends at $1D00.
For Atari DOS, version 2.0S, DOS.SYS ends at $1A7C (to accommodate “MINI-DUP,” the routine which handles MEM.SAV and loads the main DUP.SYS).
But, fortuitiously, whether by design or by chance, both MINI-DUP and CP begin at $1540. Thus, we have locations $1501 through $153F for patch space. Not a huge patch space, but patch space nevertheless. So, I would suggest that you add the following two lines to the front of the listing given above:
DEFAULT = $1501 PATCH = $1502
This means, then, that you must put a valid disk drive number (1 through the number of drives you have) into location $1501 before using a drive specifier of “D0:”.
So, how do we make and save this patch? If you have an assembler capable of doing memory-to-memory assemblies (e.g., the cartridge, EASMD, MAC/65, etc.), I would suggest typing in the lines given and actually assembling the code directly in place. (Doing the memory-to-memory assembly avoids doing FMS accesses while patching FMS…safety first!) Then, with the patch in place, use the Write-DOS-Files option (of Atari DOS, or use INIT to rewrite DOS.SYS with OS/A+) to save your patched system.
Does it work? Sure does. I wrote all the above and then went over to the machine and typed it in. Worked first time! Is it handy? Only time will tell.
And one more point. If you do have OS/A+, you will note that the Command Processor (CP) already supports the concept of a default drive. Why not use that same default drive specifier for our “D0:” trick? The only difference is that CP stores that default specifier as an ASCII character (‘1’, ‘2’, etc.), so we must look at only the low order bits of the default (and we must obtain it from its memory location according to OS/A+ rules). So here’s another version of the same patch, specifically for OS/A+, version 2:
PATCH = $1501 CPALOC = $0A DEFAULT = 8 DCBDRV = $301 *= $1176 JSR PATCH PATCH BNE OKDNO ;drive # is non-zero LDY #DEFAULT ;offset to default drive # LDA (CPALOC),Y ;gets default in ASCII AND #$0F ;just the lower bits TAY ;where FMS expects drive# OKDNO STY DCBDRV ;the patched-over code RTS ;back to the original
And, as a postscript to all this, I would like to comment on the whole subject of adding things to DOS. So long as you can patch in place or use the limited patch space starting at $1501, you should have no problems. If, however, you want to add significant code to DOS, it will not be easy if you are using Atari DOS.
If we look at pages 94 and 95 of Inside Atari DOS, we will see the routine which begins with the label “WD0”. It is this routine which actually writes the file “DOS.SYS” to the disk. And, if you look at lines 5441 through 5449, you will see that what is written out is all of memory from $7CB through the contents of location “SASA” (which are usually $1A7C or $1D00, as noted above).
Sidelight: in a way, this is poor design, since SASA also specifies the beginning address of the disk buffers. If you move the disk buffers (e.g., to the top of memory) and then try to write the DOS file(s), you might be writing out much more than you bargained for. You might want to change those compares to
if you are doing hefty modifications.
Anyway, with Atari DOS, you can’t really add on to the end of the DOS.SYS since DUP.SYS begins immediately after it in memory and would overwrite your additions. With OS/A+, though, you could add stuff at $1D00 (or wherever SASA points to) and move SASA up (which not incidentally will thus move the buffers out of the way of your addition).
I was reminded by all of the above of another “feature” of Atari DOS (and, yes, OS/A+) which is not well documented. In particular, would you like your program (including one written in BASIC) to be able to write (or rewrite) the “DOS.SYS” file? In the unlikely case that your answer is “yes,” read on.
Strange but true: when you OPEN the file named “DOS.SYS” for output (i.e., mode 8 only), right then and there the FMS will automatically write the complete boot (sectors 1, 2, and 3) and the file “DOS.SYS” to the disk! You do not have to copy anything from memory to disk, from disk to disk, or what have you. FMS does it all! (And that explains why Atari DOS won’t let you copy to a file called “DOS.SYS”.)
Thus, from BASIC, you could initialize a disk AND write the DOS.SYS file via the following simple code:
10 XIO 254,#1,0,0,"Dn:" 20 OPEN #1,8,0,"Dn:DOS.SYS" 30 CLOSE #1
Of course, the “n” can be any valid disk number (including 0, if you applied the patches discussed in the first section of this column). Also, you can omit line 10 if you don’t want to initialize the disk.
Unfortunately, this procedure will not place “DUP.SYS” on the disk if you are using Atari DOS, so you will still have to somehow copy it. (But you can use AUTORUN.SYS based systems without DUP.SYS, of course.) Again, though, if you are using OS/A+ you don’t (and can’t) use a DUP.SYS file, so the above little program will perform all you need to initialize a master, bootable disk.
Postscript: If you really need to copy a “DOS.SYS” file from one disk to another (because, for example, you don’t want to boot the version that you are copying), you can simply rename “DOS.SYS” to something else (“GORP.SYS”, for example), perform the copy, and then rename both the old and new “GORP.SYS” back to “DOS.SYS”. Thanks to the peculiarities of FMS, this method will even cause the three boot sectors to be updated to point to your new DOS file.
COMPUTE! ISSUE 34 / MARCH 1983 / PAGE 192
This month we’ll start a major project: a pseudo-BASIC interpreter written in Atari BASIC. Will this be a useful product? No. First, since it is written in and interpreted by Atari BASIC, it will of necessity be much slower than even Atari BASIC. Second, it will be an extremely limited language (as we’ll shortly see) and, in fact, a nonstandard language.
But suppose we could overcome the first objection (speed) and ignore the second (so what if it is nonstandard, as long as it is ours). Would it be useful then? Sure. In fact, we could even speculate on rewriting the interpreter in C/65 or assembly language and ending up with an extremely fast, presumably integer-only interpreter. Still, the language is limited, and it would have to have some major extensions added before it would be really usable.
Enough speculation. Let’s proceed to the language’s definition.
Whew! Feel restricted? Well, if you are adventuresome, you can try adding to and modifying the interpreter. It is a good exercise in logic, and you might even get good enough at it to give us a scare.
And one more thing before we get started with the heavy stuff. What do we call this thing? I haven’t come up with anything better than BAIT, which is my acronym for BASIC (Almost) InTerpreter. (And which is also meant to imply that it is bait: I am fishing for innovation and interest from you, my gentle readers.)
Remember: only the first character of each statement/command name is significant, so what I am really presenting here is a list of which letters of the alphabet we are going to use. The table below lists the first letter, the mnemoic I am using, the syntax of the statement, and (in parentheses) the Atari BASIC equivalent, if indeed that BAIT statement is not the same.
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)
A few of the statements need explanation, which is given below. Also, note that line-numbers and addresses, as used in the above syntax, may always be general expressions.
“Accept” allows only a single variable per use, unlike “INPUT” which allows several variables separated by commas.
“Fetch” and “Store” are complementary statements, both with the form of Atari BASIC’s “POKE.” The only difference is that “Fetch” obviously needs a variable (instead of an expression) to place the fetched (PEEKed) byte into.
“If” does not use a “THEN” keyword. Instead, any BAIT statement may follow the comma.
“Let” is a required keyword in BAIT. Actually, you may have already presumed this, since otherwise there is no way to distinguish a statement letter from a variable letter in such an assignment statement.
“Print” allows only one item to be printed per statement. Not shown in the above syntax, but allowed by BAIT, are the trailing semicolons or trailing commas, which have the same meaning as under Atari BASIC.
A discussion of what constitutes a valid expression, as well as several other more esoteric points, will have to wait for following month(s).
Since the code for BAIT will be presented in pieces over the course of several months, we must start with a coherent scheme. Also, since we will not reprint this month’s code next month (for example), the listings must merge properly and neatly.
To this end, I have designated several line number ranges for specific purposes, as listed below.
Sidelight: What are the major differences between this scheme and that actually used by the authors of Atari BASIC? (1) There is no provision for generalized I/O routines. (2) Atari BASIC checks the syntax of each line as it is entered and tokenizes it into internal form right then and there. BAIT simply stores exactly what you type in. (3) BAIT is missing many, many of BASIC’s capabilities, as noted above.
This program is my offering for this month. It consists primarily of the program editor, including the initialization need thereby.
One note about some temporary code: In the finished BAIT, lines 4000 through 4999 will control which statement/command will be executed next. In the case of a command (direct statement, in Atari parlance), these lines will pass control back to the ready prompt when the particular command executor returns. For program editing, we really only need one command, “Display” (LIST), so we have provided a very simple execute control which assumes that all direct statements are a request for “Display.”
And now for some commentary on the code. Each section of comment is preceded by the line number (or range of numbers) that it refers to.
1010. I chose a practical number here. The larger MAXLINE is, the slower the line deletion process, and the larger the memory you will need. But feel free to change it.
1020. BUFFER$ is used to hold the program you type in and can be almost any size, but be careful: I have not put any provisions in the current BAIT code for detecting when you run out of space.
1030. This is a departure from Atari BASIC (and an effective, though memory-consuming one). Rather than scanning through the program space (BUFFER$) for a line, we “know” where it is via a table kept in LINES.
2360. Since I can’t suppress the question mark which the INPUT on line 2300 produces, it is possible that using the Atari cursor keys will sometimes cause the “?” prompt to appear at the beginning of an input line. This gets rid of it by moving the right hand part of the string to the left. (It really works! Try it. And it’s also used in line 2720.)
2520 and 2630. Remember, a completed FOR/NEXT loop exits with the loop variable already changed to the first failing value (thus LL+1 in this example).
2710. If we don’t do this, and if LP is greater than LL (i.e., if there is nothing following the line number), then the reference to LINE$(LP) in line 2720 gives us a string length error.
3020. Necessary, if we stripped off the line number.
3040. Shame on you. You typed in a line number with a decimal point, trying to fool me. Gotcha.
3060. The only error message in this month’s code.
3110. If the line doesn’t yet exist, we can’t delete it.
3120, 3130. The number stored in the “LINES” table is the length of the line as stored in “BUFFER$” added to 1000 times its starting position in “BUFFER$”. We could have used two arrays (one for starting position and one for length) to make it neater, but it would have used a lot more memory.
3140. This line might not work, thanks to a bug in Atari BASIC. Perhaps next month we will have a fix to work around the bug. In the meantime, small programs in BAIT will always work. (Same as the my-system-went-away-when-I-deleted-a-line problem in Atari BASIC.)
3160–3180. This is tricky. After you remove a line via 3140, the starting position of all lines above it in the buffer must be adjusted downward by the size of the line deleted. Can you follow line 3170? Remember, “START” and “LENGTH” refer to the former start and length of the deleted line.
3210. In case we typed in just a line number.
3220–3240. Notice that each new line overlays the “*” which we tack onto the end of the buffer. We then have to put the “*” back on the end. This insures that line 3140 will always work properly, even when we delete the last line in the buffer.
3250. See the comments about lines 3120 and 3130.
3310. If it wasn’t a direct line, assume it was added to the program and go after another line.
10100–10150. We check all possible line numbers to see if they need to be listed. Note the similarity between this code and the code needed to delete a line (lines 3110 through 3130): in both cases we need the starting position and length of the line.
10190. Note how each statement will simply RETURN to the execute control code.
Still with me? Go try it. Type it in very carefully, backing yourself up every 20 lines or so. If it doesn’t work, go back and examine what you typed in, because I guarantee that it worked just seconds before I made this listing for COMPUTE!.
Next month, we will try our hand at adding Execute Expression (the most complicated part of what is left) and Print (so we can verify that expressions are executing).
1000 REM ..INITIALIZATION.. 1001 REM .................. 1010 MAXLINE=99 1020 DIM BUFFER$(5000),LINE$(128) 1030 DIM LINES(MAXLINE) 1040 FOR LP=0 TO MAXLINE:LINES(LP)=0:NEXT LP 1050 BUFFER$="*" 1500 REM LINE NUMBERS OF EXECUTION ROUTINES 1510 PROMPT=2100:INNEXT=2300 1550 DODISPLAY=10100 2000 REM ..INTERACTION.. 2001 REM ............... 2100 PRINT "READY" 2300 INPUT LINE$ 2350 IF LEN(LINE$)=0 THEN GOTO INNEXT 2360 IF LINE$(1,1)="?" THEN LINE$=LINE$(1):GOTO 2350 2370 LL=LEN(LINE$) 2500 REM CHECK FOR LINE NUMBER 2510 FOR LP=1 TO LL 2520 IF LINE$(LP,LP)<="9" AND LINE$(LP,LP)>="0" THEN NEXT LP 2550 REM LP HAS POSITION OF FIRST NON-NUMERIC CHARACTER 2560 CURLINE=0 2570 IF LP>1 THEN CURLINE=VAL(LINE$(1,LP-1)) 2600 REM NOW SKIP LEADING SPACES, IF ANY 2610 IF LP>LL THEN 2700 2620 FOR LP=LP TO LL 2630 IF LINE$(LP,LP)=" " THEN NEXT LP 2699 REM 2700 REM REMOVE LINE NUMBER AND LEADING SPA CES 2710 IF LP>LL THEN LINE$="":GOTO 3000 2720 LINE$=LINE$(LP) 3000 REM ..EDITING.. 3001 REM ........... 3010 REM IF HERE, LINE NUMBER IS IN CURLINE 3020 LL=LEN(LINE$):REM AND LL IS LENGTH THE REOF 3030 IF CURLINE=0 AND LL=0 THEN GOTO PROMPT 3040 IF CURLINE<>INT(CURLINE) THEN 3060 3050 IF CURLINE<=MAXLINE THEN 3100 3060 PRINT "***BAD LINE NUMBER***" 3070 GOTO PROMPT 3100 REM FIRST, DELETE CURLINE IF IT ALREADY EXISTS 3110 LENGTH=LINES(CURLINE):IF LENGTH=0 THEN 3200 3120 START=INT(LENGTH/1000) 3130 LENGTH=LENGTH-1000*START 3140 BUFFER$(START)=BUFFER$(START+LENGTH) 3150 LINES(CURLINE)=0 3160 FOR LP=1 TO MAXLINE:TEMP=LINES(LP) 3170 IF TEMP>=START*1000 THEN LINES(LP)=TEMP-LENGTH*1000 3180 NEXT LP 3200 REM NOW ADD LINE TO END OF BUFFER 3210 IF LL=0 THEN GOTO INNEXT 3220 START=LEN(BUFFER$) 3230 BUFFER$(START)=LINE$ 3240 BUFFER$(LEN(BUFFER$)+1)="*" 3250 LINES(CURLINE)=START*1000+LL 3300 REM NOW LINE IS IN BUFFER...WHAT DO WE DO 3310 IF CURLINE THEN GOTO INNEXT 3320 REM **** TEMPORARY: JUST FALL THROUGH ~ TO 4000 **** 4000 REM ..EXECUTE CONTROL.. 4001 REM ................... 4010 GOSUB DODISPLAY 4020 BUFFER$(INT(LINES(0)/1000))="*" 4030 LINES(0)=0 4040 GOTO PROMPT 4050 REM **** 4010 THRU 4050 ARE TEMPORARY ~ **** 5000 REM ..EXECUTE EXPRESSION.. 5001 REM ...................... 8000 REM ..MISCELLANEOUS SUBROUTINES.. 8001 REM ............................. 10000 REM ..EXECUTE THE VARIOUS STATEMENTS.. 10001 REM .................................. 10100 REM == EXECUTE DISPLAY == 10110 FOR LP=1 TO MAXLINE 10120 LENGTH=LINES(LP):IF LENGTH=0 THEN 10150 10130 START=INT(LENGTH/1000):LENGTH=LENGTH-1000*START 10140 PRINT LP;" ";BUFFER$(START,START+LENGTH-1) 10150 NEXT LP 10190 RETURN
COMPUTE! ISSUE 35 / APRIL 1983 / PAGE 230
There is so much to discuss this month, what with all the new announcements from Atari and others, that I won’t waste your time with one of my cutesy little introductions, wherein I summarize—usually in one, long, run-on sentence (with many parenthesized asides) or one or two highly conjunctive paragraphs—all the things I might talk about this month, give away the punch line to various program listings, and apologize for mistakes made three or four months in the past, including mistakes that most readers never notice because (luckily) they just read the text and don’t try to type in the program (or are clever enough to wait a month or two and see what mistakes I turn up in subsequent issues).
I will note that I will break from tradition a little this month and discuss some new software releases. I know that I have said before that I would not review software, but I feel that I must make an exception when it comes to new languages, especially those that come directly from Atari.
Unbelievable! After claiming for months (years?) that they were not after the business market, Atari did a complete about-face and produced an interpreter for that most popular business language: COBOL.
Although versions of COBOL have been available for a number of years in the CP/M market, as far as I know this marks the first attempt to implement it on a 6502. The implementation itself is revolutionary, also.
Most of my regular readers will no doubt recall that I have repeatedly stated that the 6502 is a lousy machine to write a compiler for. Several groups have attempted to solve this problem by producing code for an arbitrary “p-machine” and then writing a p-code interpreter which emulates the (possibly imaginary) p-machine. Examples of this type of compiler include Atari Pascal, UCSD Pascal for the Apple, and (in a similar but not identical vein) Forth.
Now, p-code is small, and the p-code interpreter can be fairly compact and efficient. But a COBOL p-code interpreter (should we call it C-code?) would be fairly large, because of the great variety of data types, etc., that COBOL supports. So why bother with the compiler stage? Why not tokenize the user’s input, a la Atari BASIC, and directly interpret the tokens? You save a lot of space and sacrifice only a little bit of speed. Voila.
Anyway, I recognize that not too many COMPUTE! readers are COBOL aficionados, so let’s do a very short exploration of COBOL in general and Atari COBOL in particular. Insofar as possible, I will try to relate COBOL features to BASIC features.
COBOL programs are always divided into four major divisions: the identification division, the environment division, the data division, and the procedure division.
There really is nothing in BASIC to correspond to either the identification or environment divisions. The identification division is a kind of forced REMark section; its contents are usually installation and/or compiler specific. Under Atari COBOL, this division is used to specify programmer name, date of compile, and auto-boot procedures (if any). The environment division is used by the COBOL programmer to tell the compiler about the hardware configuration that the compiled code is destined for. Atari COBOL allows the user to specify whether he or she is running on a 400, 800, or 1200 computer and describe the memory configuration. One can also specify whether a color TV or a black and white monitor is in use, using either the American NTSC system or the European PAL scheme. Naturally, one can describe the kind of printer to be used (daisy wheel, dot matrix, etc.).
COBOL’s data division is at best poorly translatable to BASIC. With COBOL, one must declare all variables, while BASIC users declare only strings and arrays. Also, with COBOL, each variable has a “PICTURE” associated with it which specifies what the data the variable represents will look like when it is displayed or printed (kind of like having a built-in “PRINT USING” format for each variable). Of course, some variables are never printed or displayed, so they can be declared “COMPUTATIONAL,” without a picture associated.
Atari COBOL expands on the “PICTURE” concept by allowing the user to specify graphic modes and pictures, including the capability of declaring a variable to be a “player” or “missile” which will be automatically animated (moved both horizontally and vertically) during the VBLANK interrupt period. Of course, there is a price to pay for this flexibility: just setting up a player can require as many as 48 lines of code!
The final division (and, yes, the divisions must be kept in proper order in a COBOL program) is the procedure division. It is here that COBOL looks the most like other languages, including BASIC. There are COBOL equivalents to many BASIC statements, including GOSUB (“PERFORM”), LET (“MOVE”), IF…THEN (“IF”), and several more. Obviously, all the useful “work” of COBOL is done in the procedure division.
While COBOL does not use line numbers, it suffers some of BASIC’s problems; and the user must work to write properly structured COBOL programs. Perhaps the real beauty of COBOL is its ability to be extremely self-documenting.
How much more readable it is to say “IF SALES GREATER THAN QUOTA MOVE BONUS-AMOUNT TO BONUS IN SALARYRECORD”. And that really is legitimate COBOL code!
The most fantastic aspect to Atari COBOL is that somehow Atari managed to fit the whole thing into an 8K byte cartridge. Rumor has it that they have developed a 16-bit virtual machine that does the brunt of the work. (I don’t believe the rumor that Atari wrote COBOL in BASIC and is going to call the manual “An Introduction to BAS-BOL.” On the other hand, who can say?)
The one unfortunate aspect to Atari COBOL is that, in order to cram it all into the cartridge, they had to omit one of the four major divisions. Wisely, Atari chose to disallow all use of the procedure division, since they felt that all but the most experienced COBOL programmers would not miss it.
I received a letter from Y. D. Obon, of Erehwon, Nebraska, regarding a comment I had made many months ago about suppressing the screen clear when changing graphics modes. I had said at the time that there seemed little use in such a capability. Well, once again, I have been proved wrong.
Since my comment appeared a long time ago, and since it was written in connection with my assembly language graphics library, I will restate it in terms of Atari BASIC. If the GRAPHICS statement had been omitted from Atari BASIC, the user would have been able to perform the equivalent function by typing in the following equivalent statements:
CLOSE #6 OPEN #6,12+n,m,"S:"
In that second line, “m” is the graphics mode (e.g., m = 7 is equivalent to GRAPHICS 7). Also, “n” is 0 if full screen graphics are desired, and 16 if you want four text lines at the bottom of the screen. BASIC generates “n” for you based on the GRAPHICS mode you select; note that BASIC inverts the sense of the “+16” before performing the OPEN. (Note that the “12” is simply to tell CIO that we can do both input and output on this channel. It is not used by “S:”.)
However, “n” as shown above can take on at least one other meaning besides selecting full screen or text mode graphics. If “n” equals 32 (or 48), the screen clear which usually takes place upon changing of graphics modes is suppressed. Now I hadn’t thought this feature of much use. After all, if I had a mode 5 graphics display and attempted to change to mode 6 without clearing the memory, I would get some sort of meaningless jumble on the screen.
The program demonstrates several points, including that made in the previous paragraph. I was sorely tempted to simply dump this listing on you, without explanation, and let you try it out. But I will take pity. At the very end of this month’s column, there is an explanation of the various points made by this program. Please, please don’t read it yet! Please type the program in and run it first. One clue: not only is the program a lesson in and of itself, but it also reveals the main point of this month’s column up to this point.
Caution: Double and triple check the program before you RUN it. The effect could be disastrously scrambled if you make a mistake. On the other hand, the listing is short enough that you should be able to type it in error-free.
Actually, maybe I’m a heel instead of a sole. But I had to have a little fun with this column, and April is obviously the month for it.
I have had a few people pre-read this month’s column, and the consensus is that I should explain some of my jokes. Now, I have always felt that a joke that has to be explained isn’t funny, but maybe practical jokes are exceptions.
I would first like to point out that even while I was pulling your assorted limbs, I was trying to give you good and valid information. In the discussions of COBOL, everything I told you was accurate and truthful, except, of course, anything that referred to Atari. For instance, COBOL really does have four major divisions, and it really does support a PICTURE capability in the data division. But I think you will find Atari buying IBM (the whole company, not just the computers) before you will find them producing a COBOL for any of their current crop of machines.
Finally, the “kicker” in the description of Atari COBOL (the giveaway that it was all a joke) was the statement that the procedure division was not supported. That is roughly equivalent to leaving all statements out of Atari BASIC other than REM, DATA, and DIM!
If you have not yet typed in and RUN the BASIC program at the end of this article, you are cheating if you start reading this part. Shame, shame, on you. Actually, the program is a kind of puzzle. How and why does it act as it does? Well, the easiest way to explain is to discuss it, line by line.
Line 10: There’s nothing special about the name of the string, DIM$. I chose that name just to show that keywords are not generally reserved names in Atari BASIC. GRAPHICS 23 is actually GRAPHICS 7+16, full screen mode 7 graphics. Note that this statement will clear all screen memory used in mode 7.
Line 20: We’re going to do a loop 40 times. We READ a character from the DATA statements and use its ATASCII value as a COLOR. Trick: only the two least significant bits of a color number are used in mode 7. Thus COLOR 3 is the same as COLOR 7 is the same as COLOR ASC("G"), because ASC("G") is 71.
Line 30: We draw some nice vertical lines, each in a color determined via the READ in line 20. Remember, plotting points on an Atari really means we are turning on or off certain bits in the computer’s screen memory. Isn’t this a peculiar set of bit patterns?
Line 40: Naughty, naughty, Bill. You used an XIO 12 instead of a CLOSE! Yes, but the point is that doing a CLOSE from BASIC really is the same as an XIO 12. So we closed IOCB #6, the screen device. And what about the rest of the stuff, the ‘237,91,"*= LABEL"’? Junk. Pure junk. It is totally ignored by CLOSE, and is meant only to mislead you.
Line 50: More of the same foolishness. XIO 3 is exactly the same as an OPEN command. We are opening the screen (“S…” is the screen, the “…” are ignored, natch) on IOCB #6 (which is where GRAPHICS would open it). We choose graphics mode 1 (the second parameter), and the 44 may be thought of as 32 + 8 + 4. The 32 says don’t clear the screen memory, 8 says we can write to the device (the screen), and 4 says we can read from it (though we don’t in this program).
Line 60: So that we can leave the full screen graphics active.
Lines 70, 80, and 90: As explained above, only the two lower bits of each of these characters are used. We could have used 2,2,4,5, etc., instead, but I worked to get these in alphabetical order, to confuse you further!
And why does it work like it does? Because we are actually seeing the stuff we plotted in mode 7 in a different way. Those same bits which were used in pairs as colors are now interpreted as bytes of eight bits each which are seen as characters.
So now you know the secrets. But you still can’t see the surprising result unless you take the time to type in and run the program. Which you already did. Unless you cheated.
Well, Henny Youngman I’m not, but I hope you enjoyed this month’s foolishness. Next month, on to more serious things. Finally, we will start showing how to write self-relocatable assembly language. Until then, best wishes from the Lo Of Lirpa.
10 DIM DIM$(1):GRAPHICS 23 20 FOR X=20 TO 59:READ DIM$:COLOR ASC(DIM$) 30 PLOT X,0:DRAWTO X,91:NEXT X 40 XIO 12,#6,237,91,"* = LABEL" 50 XIO 3,#6,44,1,"SAVE D:TEST" 60 GOTO 60 70 DATA B,B,D,E,H,K,L,L,N,O,P,R,T 80 DATA B,B,E,J,J,K,L,L,L,L,L,N,N 90 DATA A,B,D,F,G,G,J,J,K,K,L,N,O,P
COMPUTE! ISSUE 36 / MAY 1983 / PAGE 198
The series on writing your own interpreter continues. In part 2, the expression evaluator and the “PRINT” statement are added to BAIT. There’s also a look at Atari’s new 1200XL computer.
We hope to introduce several new products at the West Coast Computer Faire this year, including some designed specifically for the new model 1200 Atari (of which machine I will speak more below). I can’t tell you exactly what the new products will be, but I can say that I think that those who have written software which follows the “rules” will benefit.
Which “rules”? Oh, nothing much. Just those regarding LOMEM, HIMEM, device drivers, reset vectors, break vectors, etc. If you are an author (or company) who is developing or has developed software for the Atari computers, you might want to ask Atari for a copy of the note from Howard Chan, Manager of Software Acquisition, which details what Atari considers the “untouchable” locations as well as what “vectors” are immutable. We hope to be able to reproduce that note in this column next month.
Anyway, what are we looking into in this month’s column? Obviously, we will have part two of the series on writing your own interpreter. (And if you missed part one, you must go out right now and buy the March issue! We cannot and will not recap the materials previously covered.) Also, as mentioned, I would like to briefly discuss the new Atari 1200XL machine. But first I am going to hang my head a little.
After giving everyone else (particularly Atari) a hard time about not doing things “right,” I am embarrassed to admit that I, too, did a thing definitely “un-right.”
I must start by giving credit to F. T. Meiere, President of the Indy Atari Club from Indianapolis, for not only finding my goof, but also giving me what seems to be a workable and proper fix.
The mistake occurred, not surprisingly, in my fix to the Atari RS-232 drivers, as published in this column in the December 1982 issue of COMPUTE!. It came about because of the variety of configurations that I work in. The possible combinations I use can be shown as a small array:
Atari OS/A+ OS/A+ DOS 2.0s version 2 version 4 +----------+----------+----------+ Cartridge | | | | Software | | | | +----------+----------+----------+ RAM-based | | | | software | | | | +----------+----------+----------+
Now, obviously, the vast majority of the Atari user population finds itself in the upper left box (Atari BASIC with Atari DOS). And, yet, because I really don’t like working with “MEM.SAV” and “DUP.SYS” (and the consequential swapping in and out and sometimes losing my memory and …), I generally leave that left-hand column for last. And, unfortunately, in this case I apparently didn’t even get to it. For shame.
Anyway, taking F.T. Meiere’s advice to heart, I have indeed tested the change he has proposed in several of the possible configurations. Additionally, I have looked at my original code and found out why it failed (and why this new code works). So here, without further ado, is the fix to my RS-232 fix in the form of a change to line 1990 of the assembly language code:
was: 1990 JMP (DOSINI) WRONG! now: 1990 JMP PATCH3 RIGHT!
The new Atari machine is named the “1200XL.” I suppose the “XL” is supposed to designate speed and sexiness, a la sports cars. And certainly the machine looks sleek and sexy enough; it is by far the best looking of the current crop of home computers. Were it not for the serial I/O cable, you could easily envision holding the machine in your lap while leaning back in your easy chair, admiring and caressing it as you would a glass of good wine.
Let’s look at the obvious features:
Some “features” of the machine are less obvious: none of the current Atari software will take advantage of the expanded RAM. When you bank select the RAM, all of the OS software, including the interrupt handlers, goes away, so you must provide at least a minimal OS substitute. Because the I/O space is from $D000 to $D800 (as on the 400/800), there is no way around having a “hole” in your otherwise contiguous RAM. There is no way to get at the RAM which is “under” the cartridge (this flaw is left over from the 400/800; it is a real deficiency). It uses the same old slow floating point routines.
So how do I rate the 1200XL in overall features and performance? Quite honestly, it depends entirely on what the price of the machine is. At anything under $450, it’s a terrific bargain. I feel that, given the obvious cost-cutting Atari was able to achieve, it should be able to sell for half the cost of the 800. However, the indications are that the price of the 800 will be dropped and that the 1200 will cost more than the 800. If so, buy an 800 quick!
The exception to this suggestion is if you will write in machine language or be using non-Atari languages that can take advantage of the extra 14K of RAM (now where would you get a language like that?). If you need the extra RAM, then you may have to seriously consider the 1200. Of course, by the time you read this, the price of the 1200 and the new price of the 800 should be public knowledge, so you will be able to see how accurate my forecasting is.
In March, we started the process of writing a pseudo-BASIC interpreter, which I called “BAIT.” If you don’t have that article, this month’s work will make virtually zero sense, so don’t even attempt to follow the rest of this column.
This month, as promised, we add the expression evaluator and the “PRINT” statement to BAIT. Note that the listing published here is not complete. It is meant to be added to the March listing. In a few cases, this month’s lines will over-write (be the same number as) those from March. For example, we have replaced lines 4010 through 4040 and deleted line 4050.
Before we get into the explanation of the actual listing, we need to extend our discussion of just how an interpreter—and, in particular, BAIT—works.
There are two major parts to most language interpreters: the program editor and the program executor. The March column presented BAIT’s editor. It is not fundamentally different from most BASIC editors. True, only a few BASICs that I know of use a line number table, as we did for BAIT (some that do include Cromemco 32K Structured BASIC, which we wrote, and Data General’s Business BASIC, both designed for relatively large machines). But, to be fair, BAIT cheats by using a very small fixed number of possible line numbers.
The editor used by Atari BASIC and BASIC A+ (and Cromemco and DG BASICs) does, however, differ markedly from BAIT’s editor in one important apsect. In these more sophisticated BASICs, the user’s program line is scanned for correct syntax as it is entered and automatically converted to more usable internal “tokens.” Of course, BAIT should not be chided for any deficiency here: most microcomputer BASICs (including, for example, Microsoft BASICs) do not do any syntax checking at entry (nor do they tokenize anything except, perhaps, recognized keywords). In any case, BAIT’s editor seems quite adequate to me.
This month, we begin the second major part of an interpreter: the program executor. Not surprisingly, the program executor is much larger and more complex than the editor. In fact, we need to break the executor down into manageable hunks. I think an outline would be useful here.
This month, we will add parts C, D, and B to BAIT. (Note that we did part A in March and faked C.) Actually, part C and part B are so intimately entwined in BAIT that it is hard to see where one begins and the other leaves off, but that doesn’t make our outline any less valid.
Not shown in the above outline are the major routines which are common to the execution of most statements. To illustrate, first consider these two BAIT statements:
L A = 7*13 (Let A = 7*13) P A + 5 (Print A + 5)
What do these two statements have in common? An expression. From BAIT’s viewpoint, the two expressions here are “7*13” and “A + 5”. A major portion of BAIT (and, indeed, a major portion of any language) is the subroutine known as “EXecute EXPression,” which resides in lines 5000 through 5999 in the accompanying listing. Actually, EXEXP in BAIT is fairly simple when compared to that of Atari BASIC. Remember the rules from last month? No functions, no precedence of operators, no arrays, no strings.
Not surprisingly, almost all BAIT statements call the EXEXP subroutine. In turn, EXEXP calls a couple of routines, including GETNC (GET Next Character—lines 8100 to 8160). GETNC is perhaps the lowest level routine of the program execution phase of BAIT. It simply scans the program memory for the next non-space character, tests to see if it is an alphabetic character, and protests when the line runs out of characters.
EXEXP uses GETNC (line 5100) to find any ALPHAbetic characters in an expression; such characters are assumed to be variables (lines 5300, 5310). If instead, GETNC found a numeric character (line 5110), EXEXP backs up and scans for the entire number (lines 5400 to 5450). Only digits and a decimal point are allowed (line 5430); but there is a flaw (read that as bug) here that allows, but ignores, more than one decimal point and the digits which might follow. Finally, if the character is neither alphabetic nor numeric, BAIT assumes that it is an operator and figures out which one (lines 5120 to 5230). If it is not an operator, and if the expression was valid, EXEXP returns to its caller (line 5160).
Note that in the case of either a variable or a numeric literal, EXEXP assumes that it has received the second argument of an expression of the form “arg1 op arg2” (lines 5500 through 5530). Of course, in the case of the very first argument in any expression, there has been no preceding argument. But EXEXP takes care of that by providing a dummy argument (“0”) and a dummy operator (“+”) in its initialization code (line 5010). Incidentally, if EXEXP detects two operators or two arguments in a row, it rules the expression invalid (lines 5210, 5220, and 5510). Similarly, null expressions and expressions ending in an operator are illegal (lines 5230, 5530, and 5160).
Finally, the actual operators of BAIT are “simulated” via Atari BASIC in lines 5610 through 5680. Note that BAIT allows BASIC’s operators “+”, “-”, “*”, “/”, “>”, “<”, and “=”. BAIT simplifies the inequality sign to “#”, instead of BASIC’s “<>”. (But did you know that many, many of the early BASICs used or allowed “#” as an alternative to “<>”?)
Normally, I wouldn’t be so bold as to suggest changing an entire section of code, but I think the clumsiness of EXEXP deserves at least one alternative idea. If you are using BASIC A+ (or any BASIC with a “FIND” or “SUBSTRing” function), you could replace lines 5120 to 5128 with a single line of code:
5120 OP=FIND("+-*/>=",C$,0):IF OP THEN 5200
Of course, one could have achieved similar results with a string and a FOR/NEXT loop under Atari BASIC, but that would have slowed down EXEXP even more than it already is.
Lines 10200 through 10330 comprise the execution of “Print” under BAIT. Notice that DOPRINT also uses GETNC (line 10210). Here, we are looking to see whether a quoted string (line 10220), an expression (line 10240), or nothing at all (line 10210) follows the “P” keyword. (Or should we call it a key-letter?)
Literal strings are fairly simple to handle. Starting at the character after the quote mark, we simply loop through the buffered line printing characters as we go and looking for an ending quote (lines 10300 and 10310). If no matching quote is found, it is not an error, just as with Atari BASIC (end of line 10310). If the quote is found, we adjust the character pointer and look for a trailing semi-colon or comma (lines 10320, 10330, then 10250 to 10280).
And, strangely enough, arithmetic expressions are the easiest of all things to print. We simply call EXEXP and display the calculated result (line 10240), falling through to the trailing semi-colon and comma check. (Of course, if we were writing in assembly language, we would have to write the “display a numeric result in ASCII” routine, but even here the Atari OS ROMs would help us.)
Finally, we must comment on the other code that was added this month. Most of it, of course, was needed to support the EXEXP and DOPRINT routines. However, some of it certainly is obscure enough to bear explanation. As we did in March, we will comment on the code by line number(s).
1100. C$ is used to capture the next character by GETNC. The array VARIABLES is designed to hold 26 variables (A-Z). One could easily amend this to any multiple of 26 and allow variable names of the form A1, A2, etc.
1110. This is kind of silly. In the final code, all variables will be initialized to zero. However, since we do not yet have a “Let” statement, I wanted to give each variable a unique value so we could use it in “Print”. Hence, A = 1, B = 2, C = 3, etc.
1120. Simply a place to stuff an error message.
1520 to 1550. The line numbers of some of our more important routines.
1710. I hate using “TRAP 40000”. I like “TRAP UNTRAP” much better.
2360. The only line I actually corrected from the March listing. Do you see what the bug was?
3320. Just changed the comment to make more sense.
4010 to 4040. The beginnings of our “Line execution” control routine. We get the starting and ending positions of the current line. If the line doesn’t exist, we try for the next line. If this is a direct line, we flag it for later detection (line 4040).
4210. As things sit now, if we get here we are ready to execute the direct statement. It had better be the “P” (Print) key-letter.
4220. Why call line 4900? Why not do it in-line right here? Wait until next month.
4610. If we didn’t just execute a direct line, we go do another line. (Won’t happen this month.)
4620 to 4640. This code was at lines 4010 to 4040 last month. It just cleans up the program buffer for use by the editor.
4910. Read line 4920.
5010 to 8160. Described in the text above.
8200 to 8290. Why do this several places where a single routine will do? Note line 8240: Atari BASIC does a similar thing with the 6502’s CPU stack when it encounters an error. Why try to recover through who knows how many subroutine calls when one can simply reset the stack to the top and ignore them?
10200 to 10330. Described in the text above.
Again, BAIT seems to work as designed up to this point. You can type in program lines (with preceding line numbers) or you can type in a direct statement. Unfortunately, all direct statements are assumed to be “Print,” but just wait until next month.
And just what can you “Print”? Virtually any numeric expression that uses the BAIT operators and literal numbers. Of course, you can also use the variable letters “A” through “Z,” but this month you will get the artificial values they contain. To get you started, here are some statements to try when you get BAIT’s “ready” prompt:
P "HI THERE" P "HI THERE", P "HI THERE"; P 1+2+3+4 P 1+2+3+4 P A+B+C+D P 4>5 P 4<5 P 1/3 P 1/2 = 0.5 P 1/2 # 0.5 P 1/3;
And one last P.S., a kind of taste of what’s to come. Once you have the listing working and saved, try adding one line:
4905 IF C$="D" THEN GOTO DODISPLAY
If you don’t see what it allows, then wait for next month.
Naturally, we will have Part 3 of BAIT. We will actually begin running BAIT programs, and we will add about half of the remaining BAIT statements to our vocabulary.
Unless something else hits me in the next week or two, I think I will respond to my own challenge and begin talking about how to write self-relocatable assembly language.
1100 DIM C$(1),VARIABLES(26) 1110 FOR ALPHA=0 TO 26:VARIABLES(ALPHA)=ALPHA:NEXT ALPHA 1120 DIM ERR$(40) 1520 LET GETNC=8100 1530 SYNTAX=8300:ERROR=8200:EXEXP=5000 1550 DODISPLAY=10100:DOPRINT=10200 1700 REM MISCELLANY 1710 UNTRAP=40000 2360 IF LINE$(1,1)="?" THEN LINE$=LINE$(2):GOTO 2350 3320 REM NOTE THAT CURLINE=0 AS WE FALL TO LINE 4000 4010 LENGTH=LINES(CURLINE):IF LENGTH=0 THEN 4600 4020 CURLOC=INT(LENGTH/1000):LENGTH=LENGTH-1000*CURLOC 4030 CUREND=CURLOC+LENGTH-1 4040 IF CURLINE=0 THEN CURLINE=-1 <<< DELETE LINE 4050>>> 4100 REM READY TO EXECUTE A LINE 4200 REM EXECUTE THE STATEMENT 4210 GOSUB GETNC:IF NOT ALPHA THEN GOTO SYNTAX 4220 GOSUB 4900 4600 REM COME HERE FOR NEXT LINE 4610 CURLINE=CURLINE+1:IF CURLINE>0 THEN 4 000 4620 BUFFER$(INT(LINES(0)/1000))="*" 4630 LINES(0)=0 4640 GOTO PROMPT 4900 REM THE STATEMENT CALLER 4910 GOTO DOPRINT 4920 REM LINE 4910 IS TEMPORARY 2!!! 5010 EVAL=0:LASTOP=-1 5020 VALID=0 5100 GOSUB GETNC:IF ALPHA THEN 5300 5110 IF C$>="0" AND C$<="9" THEN 5400 5120 REM WHICH OPERATOR? 5121 IF C$="+" THEN OP=1:GOTO 5200 5122 IF C$="-" THEN OP=2:GOTO 5200 5123 IF C$="*" THEN OP=3:GOTO 5200 5124 IF C$="/" THEN OP=4:GOTO 5200 5125 IF C$=">" THEN OP=5:GOTO 5200 5126 IF C$="<" THEN OP=6:GOTO 5200 5127 IF C$="=" THEN OP=7:GOTO 5200 5128 IF C$="#" THEN OP=8:GOTO 5200 5160 IF VALID THEN RETURN 5170 GOTO 5900 5200 REN GOT AN OPERATOR 5210 IF LASTOP>0 THEN 5170 5220 IF LASTOP<0 AND OP>2 THEN 5170 5230 LASTOP=OP:VALID=0:GOTO 5100 5300 REM GOT A VARIABLE 5310 VAL2=VARIABLES(ALPHA):GOTO 5500 5400 REM GOT A NUMERIC 5410 CURLOC=CURLOC-1:REM BACKUP TO FIRST NUMERIC 5420 FOR LL=CURLOC TO CUREND:C$=BUFFER$(LL) 5430 IF (C$>="0" AND C$<="9") OR C$="." THEN NEXT LL 5440 VAL2=VAL(BUFFER?(CURLOC,LL-1)) 5450 CURLOC=LL 5500 REM VAR OR NUMERIC 5510 IF LASTOP=0 OR ABS(LASTOP)>8 THEN 5900 5520 GOSUB 5600+10*ABS(LASTOP) 5530 LASTOP=0:VALID=1:GOTO 5100 5600 REM EXECUTE OPERATORS 5610 EVAL=EVAL+VAL2:RETURN 5620 EVAL=EVAL-VAL2:RETURN 5630 EVAL=EVAL*VAL2:RETURN 5640 EVAL=EVAL/VAL2:RETURN 5650 EVAL=(EVAL>VAL2):RETURN 5660 EVAL=(EVAL<VAL2):RETURN 5670 EVAL=(EVAL=VAL2):RETURN 5680 EVAL=(EVAL<>VAL2) :RETURN 5900 ERR$="INVALID EXPRESSION":GOTO ERROR 8100 REM GETNC 8110 IF CURLOOCUREND THEN C=-1:C$=CHR$(155):GOTO 8140 8120 C=ASC(BUFFER$(CURLOC)):C$=CHRS(C) 8130 CURLOC=CURLOC+1 8140 IF C=32 THEN GOTO GETNC 8150 ALPHA=(C$>="A" AND C$<="Z")*(C-64) 8160 RETURN 8200 REM ERROR ROUTINE 8210 PRINT :PRINT "***";ERR$;"***"; 8220 IF CURLINE>0 THEN PRINT " AT LINE ";CURLINE 8230 PRINT :TRAP 8250 8240 POP :POP :POP :POP :POP :POP :POP :POP 8250 TRAP UNTRAP 8290 GOTO PROMPT 8300 REM SYNTAX ERROR 8310 ERR$="SYNTAX ERROR":GOTO 8200 10200 REM ==EXECUTE PRINT== 10210 GOSUB GETNC:IF C<0 THEN PRINT :RETURN 10220 IF C=34 THEN 10300 10230 CURLOC=CURLOC-1 10240 GOSUB EXEXP:PRINT EVAL; 10250 IF C$=";" THEN RETURN 10260 IF C$="/" THEN PRINT ,:RETURN 10270 IF C<0 THEN PRINT :RETURN 10280 GOTO SYNTAX 10300 FOR LL=CURLOC TO CUREND:C$=BUFFER$(LL) 10310 IF ASC(C$)<>34 THEN PRINT C$;:NEXT LL:PRINT :RETURN 10320 CURLOC=LL+1:GOSUB GETNC 10330 GOTO 10250
COMPUTE! ISSUE 37 / JUNE 1983 / PAGE 214
This month Bill continues with the creation of the BAIT interpreter (Basic Almost InTerpreter). And he includes some comments from readers.
For those of you who may have missed Parts 1 and 2, let me give a brief description of this project. BAIT is an acronym for Basic Almost InTerpreter. It is a pseudo-BASIC actually written in Atari BASIC. It is slow. It uses one letter commands (for example, “P” for PRINT). It is simple. And its purpose is simply to give you an inkling of how a BASIC interpreter works. It is not a finished, usable language.
This month we will study Part 3 of this listing. We will publish only those lines which have changed from Parts 1 and 2. However, next month we will present Part 4, the last part, and we’ll publish the entire listing.
Before starting on my own comments about and additions to BAIT this month, though, I would like to share some reader comments on Part 1.
First, Howard Fishman of Brooklyn, New York, pointed out that I could eliminate the question mark prompt from the INPUT statement by simply using OPEN #3,12,0,“E:” at the beginning of the program and then replacing INPUT with INPUT#3.
Sigh. How right you are, Howard. The funny thing is that I remember discovering this technique about three years ago on our Apple II version of OSS BASIC. How soon we forget. I will incorporate his suggestion in the finished version of BAIT.
Also, Howard protested my not including a facility to list BAIT programs to disk and retrieve them. Perhaps I might change my mind later, but for now I feel that adding that code is an excellent exercise for the reader.
The second letter was from Donald Biresch of Ottsville, Pennsylvania. His comment was that he wished I wouldn’t “spend [my] time … writing about creating BASIC interpreters (something … less than 1 percent of the end user market has any interest in).” Is he right or wrong? Wrong, I hope, though I admit I have sometimes regretted starting this project, since it has proven to be a larger program than envisioned.
Still, I believe that the subject interests more than 1 percent of you, even if my readers aren’t necessarily typical “end users.” In particular, I think the BAIT articles are a good lead-in to a more serious study of a BASIC interpreter.
However, if Donald is correct, I apologize. Let me know how you feel.
As with the previous parts, I will describe this month’s changes by line number or line number range.
1110. We set all variables to zero.
1515 to 1580. These are simply some line number equates for use as the objects of GOTOs or GOSUBs. Note, though, that they help produce readable code.
3060. Just centralizing some error messages.
4200 to 4250. A complete restructuring of the “Execute Next Statement” routines. Note that multiple statements per line are now legal. Also, note that pushing the START button now serves as a program break (the BREAK key still stops BASIC itself).
4610 to 4620. Sometimes when you generalize things, the program gets simpler. Direct and deferred execution are now virtually identical.
4700 to 4730. After executing a direct line, we wipe it out of the program memory.
4910 to 4960. Look at all the wonderful statements we can now use! They are in order here. Thus a statement “A” will cause DO ACCEPT to be called, etc.
8290. More clean up.
8400 to 8410. Ditto. See line 3060.
10190. Now, we exit from the statement “DO” routines only after getting the character which terminates the statement (that is, the colon or return character).
10250 to 10270. Ditto. Just making PRINT’s code cleaner.
10400 to 10420. Look how easy BEGIN (same as BASIC’s RUN) is! We zero out the variables, set the current line number to zero, say we found an end of line, and let execute-next-line (at 4600) start the program execution.
10500 to 10530. GOTO is almost as simple. Find what line number the user wants and fool execute-next-line into getting the next execution line from there.
10600 to 10650. LET is only a little more complex. It insists on a variable (10610) for a destination (10620), an equal sign (10630), and an expression (10640). Then it simply gives the destination variable the value of the expression.
10700 to 10730. IF is, I think, a little clever. It simply tells the get-next-statement code (4240 and 4250) that the next character is an end of line if the user’s expression evaluates to zero. Otherwise, it does nothing, and the next statement (if any) gets executed.
10800 to 10910. ACCEPT and CALL will be implemented next month.
11000 to 11030. END simply forces an end of line character and an illegal next line number value. The direct statement test (line 4620) effectively ends the program.
11100 to 11410. FETCH, NEW, RETURN, and STORE are left for next month.
Well, there you have it. A functional, albeit minimal interpreter. If you have typed it all in properly, you might try the following program as a test of its logic.
1 P"N",:P"N+N",:P"N*N" 2 PN,:PN+N,:PN*N 3 LN=N+1 4 IN<20:G2 5 E B
And, for those of you who have not followed BAIT up until now, that translates roughly into BASIC as:
1 PRINT "N","N+N","N*N" 2 PRINT N,N+N,N*N 3 LET N=N+1 4 IF N<20 THEN GOTO 2 5 END RUN
And that’s enough BAIT for this month. If you don’t do anything else while waiting for next month’s column, you might try writing the code to execute NEW. It will be extremely simple.
BAIT 1110 FOR ALPHA=0 TO 26:VARIABLES(ALPHA)=0:NEXT ALPHA 1515 DIRECT=4700:BADLINE=8400 1560 DOBEGIN=10400:DOGOTO=10500:DOLET=10600:DOIF=10700 1570 DOACCEPT=10800:DOCALL=10900:DOEND=11000:DOFETCH=11100 1580 DONEW=11200:DORETURN=11300:LET DOSTORE=11400 3060 GOTO BADLINE <<< DELETE LINE 3070 >>> 4200 REM EXECUTE A SINGLE STATEMENT 4230 IF PEEK(53279)<>7 THEN GOSUB DOEND 4240 IF C$=":" THEN 4200 4250 IF C>=0 THEN GOTO SYNTAX 4610 CURLINE=CURLINE+1 4620 IF CURLINE>0 AND CURLINE<=MAXLINE THEN 4000 <<< DELETE LINE 10280 >>> 4700 REM ===COME HERE ON END OF DIRECT LINE EXECUTE=== 4710 IF LINES(0) THEN BUFFER$(INT(LINES(0)1000))="*" 4720 LINES(0)=0 4730 GOTO PROMPT 4910 ERR$="BAD STATEMENT NAME" 4920 ON ALPHA GOTO DOACCEPT,DOBEGIN,DOCALL,DODISPLAY,DOEND 4930 ON ALPHA-5 GOTO DOFETCH,DOGOTO,ERROR,DOIF,ERROR,ERROR 4940 ON ALPHA-11 GOTO DOLET,ERROR,DONEW,ERROR,DOPRINT 4950 ON ALPHA-16 GOTO ERROR,DORETURN,DOSTORE 4960 GOTO ERROR 8290 GOTO DIRECT 8400 REM BAD LINE NUMBER 8410 ERR$="BAD LINE NUMBER":GOTO 8200 10190 GOTO GETNC 10250 IF C$=";"THEN GOTO GETNC 10260 IF C$=","THEN PRINT ,:GOTO GETNC 10270 PRINT :RETURN <<< DELETE 4630 >>> <<< DELETE 4640 >>> 10400 REM ===EXECUTE BEGIN=== 10410 FOR ALPHA=0 TO 26:VARIABLES(ALPHA)=0:NEXT ALPHA 10420 CURLINE=0:C=-1:RETURN 10500 REM ===EXECUTE GOTO=== 10510 GOSUB EXEXP 10520 IF LINES(EVAL)=0 THEN ERR$="NO SUCH LINE":GOTO 8200 10530 CURLINE=EVAL-1:RETURN 10600 REM ===EXECUTE LET=== 10610 GOSUB GETNC:IF NOT ALPHA THEN GOTO SYNTAX 10620 DESTVAR=ALPHA 10630 GOSUB GETNC:IF C$<>"=" THEN GOTO SYNTAX 10640 GOSUB EXEXP:VARIABLES(DESTVAR)=EVAL 10650 RETURN 10700 REM ===EXECUTE IF=== 10710 GOSUB EXEXP 10720 IF NOT EVAL THEN C=-1:C$="" 10730 RETURN 10800 REM ===EXECUTE ACCEPT=== 10900 REM ===EXECUTE CALL=== 10910 GOTO ERROR 11000 REM ===EXECUTE END=== 11010 PRINT "===END AT LINE";CURLINE;"===" 11020 C=-1:CURLINE=C:C$="" 11030 RETURN 11100 REM ===EXECUTE FETCH=== 11200 REM ===EXECUTE NEW=== 11300 REM ===EXECUTE RETURN=== 11400 REM EXECUTE STORE=== 11410 GOTO ERROR
COMPUTE! ISSUE 38 / JULY 1983 / PAGE 186
A mini-series on relocatable machine language begins in this month’s column, plus a tip on a new product—an intelligent cable. Next month, the last part of the BAIT interpreter and more on relocatable machine language.
I have been working on a new project for COMPUTE! Books. By the time you read this, COMPUTE!’s Atari BASIC Sourcebook should be wending its way to your dealers’ shelves and into your hands. Like Inside Atari DOS, the Sourcebook is a complete source listing of—what else?—Atari BASIC, along with a comprehensive explanation of how and why it all works.
Enough advertising. This month we will begin a mini-series on self-relocatable machine language. But before we begin all that, time out for some ruminations.
Before we start investigating self-relocatable machine code on the 6502, I’d like to get up on my soapbox for a while and do a little preaching.
This month’s sermon was inspired by a machine language program published in another magazine. The program seemed to me the epitome of poor programming techniques. And lest it seem that I am taking a cheap shot, let me hasten to add that the program works and works well. I am carping about the printed form of the program, not the results thereof.
In the tradition of any good preacher, then, let me give you some suggestions on how to write good, readable, maintainable machine language:
Those of you with some OSS software will see that I have taken a small pot shot at our own manuals in commandment 5. Well, I never said we were perfect. (Great, maybe, but not perfect.)
And those of you with Atari’s Macro Assembler may object to using long labels since, even though AMAC allows long labels, it ignores all but the first six characters. Sorry, but I still think this rule should be followed. You just have to be more inventive to insure that labels are unique in the first six characters. (For example, IOCB. AUX1 and IOCB.AUX2 look the same to AMAC, so use IOCB.1AUX and IOCB.2AUX .)
Anyway, rather than go through each of those commandments one by one, let’s look at an example subroutine coded with both worst and best techniques.
Example 1: Worst Technique ; EXAMPLE 1: print A register *= $1F00 LDX #11 STX $342 ; put 11 in location $342 LDX #0 STX $348 STX $349 JMP $E456 ;go to $E456
Example 2: Best Technique ; ; Example 2: Output the character in the A-register ; to file channel (IOCB) number zero (assumed to be the screen). ; ; Entry: A-register contains the character ; Exit: Status of all registers unknown ; *= LOWMEMORY PRINTCHARACTER LDX #COMMAND.PUTBINARY STX IOCB.COMMAND ; command for CIO LDX #0 ; use a zero buffer length STX IOCB.LOLENGTH ;tells CIO to output STX IOCB.HILENGTH ;contents of A register ; next line commented out...not needed since X already=0 ; LDX #0 ; specify IOCB zero JSR CIO ; let CIO do the real work ; Could check for errors here RTS ; all done
Enough said? I refuse to decipher programs like Example 1. Of course, Example 2 wouldn’t be very useful either unless equates for the various labels were supplied (as in IOCB.COMMAND = $342), but at least most readers could understand its intent.
Regular readers will no doubt recall the many occasions on which I have ranted about staying out of Page 6 or about putting code at LOMEM or about writing code that is not specific to a particular hardware/software configuration. But, to be fair, sometimes it is hard to follow all of the rules, especially when adapting a program from a book or magazine.
Often, the real secret to writing adaptable code is in learning to write self-relocatable code. The techniques we will begin discussing this month are designed specifically for use with the 6502 microprocessor. While there will be several references to Atari internal structure, most of what is presented here is appropriate to Apple and Commodore machines as well.
And I will answer one more question before we start on the hard stuff: Why should we want to write self-relocatable code? Sorry, we don’t have room for that answer this month. Wait until next month. (It’s a good answer, honest!)
Actually, there is just one rule to remember in writing self-relocatable code: avoid references to absolute memory locations.
Unfortunately, this is often a very hard rule to follow. Fortunately, there are many places where we can make an exception to this rule.
For starters, look at the subroutine in Examples 1 and 2 above. Is it self-relocatable? Your first impulse might be to say no, since it references $342, $348, $349, and $E456, which are all absolute locations. And even if you do it right and use the equated labels of Example 2, they are still absolute, no matter what they look like.
But. Within the context of any given machine, there are always certain locations which never change. In particular, hardware locations, locations in ROM, and locations in the RAM (or values used and defined by ROM subroutines) cannot possibly change. An exception to this is when you plug in a new set of ROMs, and you can ask the software vendors about the fun and games the Atari 1200XL’s new ROMs are giving them.
In the example given, $E456 (CIO) is in the Atari’s OS ROM space. It is a guaranteed entry point to the OS command implementation code. It won’t change (even in the new 1200, etc.).
And locations $340 through $34F (as well as $350 through $3BF) are in the IOCB space defined by Atari for use with CIO. Again, they won’t and cannot change.
Finally, the command used (11) and the zero buffer length are values defined by the OS ROMs to have certain meanings. And if Atari changes these meanings, we are all in trouble, because Atari BASIC, PILOT, and more won’t work then.
The result of all this? No matter where you assemble that example (that is, no matter where the “*=” places the code), the resultant machine object code will be precisely the same! Presto. That example is self-relocatable.
Surprisingly, a lot of the subroutines used with Atari BASIC follow the mold shown here: they simply set up some values in the Atari-specified memory locations and call an Atari-specified OS routine. They are implicitly self-relocatable.
So what is not relocatable? Generally, the prime culprits are:
Let’s make up an example just to illustrate potential problems.
*= $600 SAVEX *= *+1 MESSAGE .BYTE 'This is the message',0 ; ; this is the same code as the examples above ; PRINTCHARACTER LDX #COMMAND.PUTBINARY STX IOCB.COMMAND ; command for CIO LDX #0 ; use a zero buffer length STX IOCB.LOLENGTH ; tells CIO to output STX IOCB.HILENGTH ; contents of A register JMP CIO ; let CIO do the real work ; ; call here to print contents of 'MESSAGE' ; Entry conditions: none ; Exit conditions: none, no registers saved PRINTMESSAGE LDX #0 STX SAVEX ; initialize message pointer MSGLOOP LDX SAVEX ; get current message pointer LDA MESSAGE,X ; get next character of msg BEQ QUIT ; but quit if it's last char JSR PRINTCHARACTER ; else print it INC SAVEX ; point to next character JMP MSGLOOP ; and do another character ; QUIT RTS ;we are done
Do you see the problem areas? If we move this routine somewhere else in memory, the addresses of MESSAGE, PRINTCHARACTER, MSGLOOP, and SAVEX all change, and the object code associated with them changes also. This routine is definitely not self-relocatable.
But let’s tackle each of the problem labels one at a time and see how we can change the references to each to make the code self-relocatable.
MSGLOOP is the easiest label to “fix.” For example, if we change the line JMP MSGLOOP to BNE MSGLOOP, the label MSGLOOP is no longer a problem (since all branch instructions are always, by nature, self-relocatable).
And we could save the X-register on the stack (via TXA and PHA) and later retrieve and increment it similarly (via PLA, TAX, and INX), thus eliminating the need for SAVEX.
The PRINTCHARACTER routine could easily be eliminated in its entirety by placing its code inline in the middle of the PRINTMESSAGE routine. This is a good solution only if PRINTCHARACTER is not called by any other routine. It may also be an adequate solution if the routine being placed in-line is fairly small (as is PRINTCHARACTER) so that you can keep two or more copies around, if necessary.
But what do we do about MESSAGE, which is too big to put in a register? Or what would we do if PRINTCHARACTER was a long routine? And, most importantly, what do we do with a hunk of self-relocatable code once we have managed to produce it?
Next month we’ll tackle those questions and others.
Do you do much work on both Apple II and Atari computers? If so, you could probably use a handy-dandy little device which we recently acquired.
Allen Prowell of Fresno, California, built us what amounts to an intelligent cable between our Apple and our Atari. It plugs into the joystick port on the Atari and into the game port on the Apple. It transfers ASCII files in either direction (doing “light conversion” on return characters, etc.). Very fast. It is much more convenient and reliable than using RS-232, and it moves over 1000 characters a second, including disk accesses.
As I said, this is a specialized product, but if you need it, call Allen (209) 227-4917. Using our C/65 and MAC/65 on both Atari and Apple, we have converted an 8K program in as little as two hours, including the transfers, assemblies, etc.
I think next month’s column will be fairly long, what with the last part of BAIT and Part 2 of self-relocatable machine language. If I have room, though, I will introduce you to a new Atari graphics mode. Also, coming soon, information on some strange and wonderful new products for the Atari.
COMPUTE! ISSUE 39 / AUGUST 1983 / PAGE 148
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.
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:
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.
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-1 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.)
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 point 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 $1D00 (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.
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:
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.
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 TABLE$ 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 TABLE$ 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., TABLE$ 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)
BAIT 1000 REM ..INITIALIZATION.. 1001 REM .................. 1010 MAXLINE=99 1020 DIM BUFFERS(5000),LINES(128) 1030 DIM LINES(MAXLINE) 1040 FOR LP=0 TO MAXLINE:LINES(LP)=0:NEXT LP 1050 BUFFER$= "*" 1100 DIM C$(1),VARIABLES(26) 1110 FOR ALPHA=0 TO 26:VARIABLES(ALPHA)=0:NEXT ALPHA 1120 DIM ERR$(40) 1130 DIM STACK(50,2):REM MAX CALLS THUS IS 50 1500 REM LINE NUMBERS OF EXECUTION ROUTINES 1510 PROMPT=2100:INNEXT=2300 1515 DIRECT=4700:BADLINE=8400 1520 LET GETNC=8100 1530 SYNTAX=8300:ERROR=8200:EXEXP=5000 1540 BADVALUE=8500 1550 DODISPLAY=10100:DOPRINT=10200 1560 DOBEGIN=10400:DOGOTO=10500:DOLET=10600:DOIF=10700 1570 DOACCEPT=10800:DOCALL=10900:DOEND=11000:DOFETCH=11100 1580 DONEW=11200:DORETURN=11300:LET DOSTORE=11400 1700 REM MISCELLANY 1710 UNTRAP=40000 1720 OPEN #5,12,0,"E:":REM SO THERE IS NO? PROMPT 2000 REM ..INTERACTION.. 2001 REM ............... 2100 PRINT "READY" 2200 STACK=0:REM CLEAN UP 'CALL' STACK 2300 INPUT #5,LINE$ 2350 IF LEN(LINE$)=0 THEN GOTO INNEXT <<< DELETED OLD LINE 2360 >>> 2370 LL=LEN(LINE$) 2500 REM CHECK FOR LINE NUMBER 2510 FOR LP=1 TO LL 2520 IF LINE$(LP,LP)<="9" AND LINE$(LP,LP)>="0" THEN NEXT LP 2550 REM LP HAS POSITION OF FIRST NON-NUMERIC CHARACTER 2560 CURLINE=0 2570 IF LP>1 THEN CURLINE=VAL(LINE$(1,LP-1)) 2600 REM NOW SKIP LEADING SPACES, IF ANY 2610 IF LP>LL THEN 2700 2620 FOR LP=LP TO LL 2630 IF LINE$(LP,LP)=" " THEN NEXT LP 2700 REM REMOVE LINE NUMBER AND LEADING SPACES 2710 IF LP>LL THEN LINE$="";GOTO 3000 2720 LINE$=LINE$(LP) 3000 REM ..EDITING.. 3001 REM ........... 3010 REM IF HERE, LINE NUMBER IS IN CURLINE 3020 LL=LEN(LINE$):REM AND LL IS LENGTH THEREOF 3030 IF CURLINE=0 AND LL=0 THEN GOTO PROMPT 3040 IF CURLINE<>INT(CURLINE) THEN 3060 3050 IF CURLINE<=MAXLINE THEN 3100 3060 GOTO BADLINE 3100 REM FIRST, DELETE CURLINE IF IT ALREADY EXISTS 3110 LENGTH=LINES(CURLINE):IF LENGTH=0 THEN 3200 3120 START=INT(LENGTH/1000) 3130 LENGTH=LENGTH-1000*START 3140 BUFFER$(START)=BUFFER$(START+LENGTH ) 3150 LINES(CURLINE)=0 3160 FOR LP=1 TO MAXLINE:TEMP=LINES(LP) 3170 IF TEMP>=START*1000 THEN LINES(LP)=TEMP-LENGTH*1000 3180 NEXT LP 3200 REM NOW ADD LINE TO END OF BUFFER 3210 IF LL=0 THEN GOTO INNEXT 3220 START=LEN(BUFFER$) 3230 BUFFER$(START)=LINES$ 3240 BUFFER$(LEN(BUFFER$)+1)= "*" 3250 LINES(CURLINE)=START*1000+LL 3300 REM NOW LINE IS IN BUFFER...WHAT DO WE DO 3310 IF CURLINE THEN GOTO INNEXT 3320 REM NOTE THAT CURLINE=0 AS WE FALL TO LINE 4000 4000 REM ..EXECUTE CONTROL.. 4001 REM ................... 4010 LENGTH=LINES(CURLINE):IF LENGTH=0 THEN 4600 4020 CURLOC=INT(LENGTH/1000):LENGTH=LENGTH-1000*CURLOC 4030 CUREND=CURLOC+LENGTH-1 4040 IF CURLINE=0 THEN CURLINE=-1 4100 REM READY TO EXECUTE A LINE 4200 REM EXECUTE A SINGLE STATEMENT 4210 GOSUB GETNC:IF NOT ALPHA THEN GOTO SYNTAX 4220 GOSUB 4900 4230 IF PEEK(53279)<>7 THEN GOSUB DOEND 4240 IF C$="THEN 4200 4250 IF C>=0 THEN GOTO SYNTAX 4600 REM COME HERE FOR NEXT LINE 4610 CURLINE=CURLINE+1 4620 IF CURLINE>0 AND CURLINE<=MAXLINE THEN 4000 4700 REM ===COME HERE ON END OF DIRECT LINE EXECUTE=== 4710 IF LINES(0) THEN BUFFER$(INT(LINES(0)/1000))="*" 4720 LINES(0)=0 4730 GOTO PROMPT 4900 REM THE STATEMENT CALLER 4910 ERR$="BAD STATEMENT NAME" 4920 ON ALPHA GOTO DOACCEPT,DOBEGIN,DOCALL,DODISPLAY,DOEND 4930 ON ALPHA-5 GOTO DOFETCH,DOGOTO,ERROR,DOIF,ERROR,ERROR 4940 ON ALPHA-11 GOTO DOLET,ERROR,DONEW,ERROR,DOPRINT 4950 ON ALPHA-16 GOTO ERROR,DORETURN,DOSTORE 4960 GOTO ERROR 5000 REM ..EXECUTE EXPRESSION.. 5001 REM ...................... 5010 EVAL=0:LASTOP=-1 5020 VALID=0 5100 GOSUB GETNC:IF ALPHA THEN 5300 5110 IF C$>="0" AND C$<="9" THEN 5400 5120 REM WHICH OPERATOR? 5121 IF C$="+" THEN OP=1:GOTO 5200 5122 IF C$="-" THEN OP=2:GOTO 5200 5123 IF C$="*" THEN OP=3:GOTO 5200 5124 IF C$="/" THEN OP=4:GOTO 5200 5125 IF C$=">" THEN OP=5:GOTO 5200 5126 IF C$="<" THEN OP=6:GOTO 5200 5127 IF C$="=" THEN OP=7:GOTO 5200 5128 IF C$="#" THEN OP=8:GOTO 5200 5160 IF VALID THEN RETURN 5170 GOTO 5900 5200 REM GOT AN OPERATOR 5210 IF LASTOP>0 THEN 5170 5220 IF LASTOP<0 AND OP>2 THEN 5170 5230 LASTOP=OP:VALID=0:GOTO 5100 5300 REM GOT A VARIABLE 5310 VAL2=VARIABLES(ALPHA):GOTO 5500 5400 REM GOT A NUMERIC 5410 CURLOC=CURLOC-1:REM BACKUP TO FIRST NUMERIC 5420 FOR LL=CURLOC TO CUREND:C$=BUFFER$(LL) 5430 IF (C$>="0" AND C$<="9") OR C$="." THEN NEXT LL 5440 VAL2=VAL(BUFFER$(CURLOC,LL-1)) 5450 CURLOC=LL 5500 REM VAR OR NUMERIC 5510 IF LASTOP=0 OR ABS(LASTOP)>8 THEN 5 900 5520 TRAP BADVALUE:GOSUB 5600+10*ABS(LAS TOP) 5530 TRAP UNTRAP:LASTOP=0:VALID=1:GOTO 5 100 5600 REM EXECUTE OPERATORS 5610 EVAL=EVAL+VAL2:RETURN 5620 EVAL=EVAL-VAL2:RETURN 5630 EVAL=EVAL*VAL2:RETURN 5640 EVAL=EVAL/VAL2:RETURN 5650 EVAL=(EVAL>VAL2):RETURN 5660 EVAL=(EVAL<VAL2):RETURN 5670 EVAL=(EVAL=VAL2):RETURN 5680 EVAL=(EVAL<>VAL2):RETURN 5900 ERR$="INVALID EXPRESSION":GOTO ERROR 8000 ..MISCELLANEOUS SUBROUTINES.. 8001 REM ......................... 8100 REM GETNC 8110 IF CURLOOCUREND THEN C=-1:C$=CHR$(155):GOTO 8140 8120 C=ASC(BUFFER$(CURLOC)):C$=CHR$(C) 8130 CURLOC=CURLOC+1 8140 IF C=32 THEN GOTO GETNC 8150 ALPHA=(C$ > = "A" AND C$< = "Z")*(C-64) 8160 RETURN 8200 REM ERROR ROUTINE 8210 PRINT :PRINT "***";ERR$;"***"; 8220 IF CURLINE>0 THEN PRINT " AT LINE ";CURLINE 8230 PRINT :TRAP 8250 8240 POP :POP :POP :POP :POP :POP :POP :POP 8250 TRAP UNTRAP 8290 GOTO DIRECT 8300 REM SYNTAX ERROR 8310 ERR$="SYNTAX ERROR" :GOTO ERROR 8400 REM BAD LINE NUMBER 8410 ERR$="BAD LINE NUMBER":GOTO ERROR 8500 REM VALUE OUT OF RANGE ERROR 8510 ERR$="BAD VALUE":GOTO ERROR 10000 REM ..EXECUTE THE VARIOUS STATEMENTS.. 10001 REM .............................. 10100 REM ==EXECUTE DISPLAY== 10110 FOR LP=1 TO MAXLINE 10120 LENGTH=LINES(LP):IF LENGTH=0 THEN 10150 10130 START=INT(LENGTH/1000):LENGTH=LENGTH-1000*START 10140 PRINT LP;" ";BUFFER$(START,START+LENGTH-1) 10150 NEXT LP 10190 GOTO GETNC 10200 REM ==EXECUTE PRINT== 10210 GOSUB GETNC:IF C<0 OR C$=":" THEN PRINT :RETURN 10220 IF C=34 THEN 10300 10230 CURLOC=CURLOC-1 10240 GOSUB EXEXP:PRINT EVAL; 10250 IF C$=";" THEN GOTO GETNC 10260 IF C$="," THEN PRINT ,:GOTO GETNC 10270 PRINT :RETURN 10300 FOR LL=CURLOC TO CUREND:C$=BUFFER$(LL) 10310 IF ASC(C$)<>34 THEN PRINT C$;:NEXT LLSPRINT :RETURN 10320 CURLOC=LL+1:GOSUB GETNC 10330 GOTO 10250 10400 REM ===EXECUTE BEGIN=== 10410 FOR ALPHA=0 TO 26:VARIABLES(ALPHA)=0:NEXT ALPHA 10420 CURLINE=0:C=-1:RETURN 10500 REM ===EXECUTE GOTO=== 10510 GOSUB EXEXP 10520 IF LINES(EVAL)=0 THEN ERR$="NO SUCH LINE":GOTO 8200 10530 CURLINE=EVAL-1 10540 C=-1:C$="" 10550 RETURN 10600 REM ===EXECUTE LET=== 10610 GOSUB GETNC:IF NOT ALPHA THEN GOTO SYNTAX 10620 DESTVAR=ALPHA 10630 GOSUB GETNC:IF C$<>"=" THEN GOTO S YNTAX 10640 GOSUB EXEXP:VARIABLES(DESTVAR)=EVAL 10650 RETURN 10700 REM ===EXECUTE IF=== 10710 GOSUB EXEXP 10720 IF NOT EVAL THEN C=-1:C$="" 10730 RETURN 10800 REM ===EXECUTE ACCEPT=== 10810 GOSUB GETNC:IF NOT ALPHA THEN GOTO SYNTAX 10820 TRAP 10850:INPUT EVAL:TRAP UNTRAP 10830 VARIABLES(ALPHA)=EVAL 10840 GOTO GETNC 10850 PRINT "??? MUST INPUT A NUMBER, REPEAT 10860 GOTO 10820 10900 REM ===EXECUTE CALL=== 10910 IF STACK=50 THEN ERR$="TOO MANY CALLS":GOTO ERROR 10920 STACK(STACK,0)=CURLOC 10930 STACK(STACK,1)=CUREND 10940 STACK(STACK,2)A=CURLINE 10950 STACK=STACK+1 10960 GOTO DOGOTO 11000 REM ===EXECUTE END=== 11010 PRINT"===END AT LINE ";CURLINE;"===" 11020 C=-1:CURLINE=C:C$="" 11030 RETURN 11100 REM ===EXECUTE FETCH=== 11110 GOSUB EXEXP 11120 IF C$<>"," THEN GOTO SYNTAX 11130 GOSUB GETNC:IF NOT ALPHA THEN GOTO SYNTAX 11140 TRAP BADVALUE:VARIABLES(ALPHA)=PEEK(EVAL) 11150 TRAP UNTRAP:GOTO GETNC 11200 REM ===EXECUTE NEW=== 11210 RUN 11300 REM ===EXECUTE RETURN=== 11310 IF STACK=0 THEN ERR$="NO MATCHING CALL":GOTO ERROR 11320 STACK=STACK-1 11330 CURLOC=STACK(STACK,0) 11340 CUREND=STACK(STACK,1) 11350 CURLINE=STACK(STACK,2) 11360 GOSUB EXEXP:REM IGNORE ... ALREADY P ROCESSED 11370 RETURN 11400 REM ===EXECUTE STORE=== 11410 GOSUB EXEXP:ADDRESS=EVAL 11420 IF C$<>"THEN GOTO SYNTAX 11430 GOSUB EXEXP 11440 TRAP BADVALUE:POKE ADDRESS,EVAL 11450 TRAP UNTRAP:RETURN
COMPUTE! ISSUE 40 / SEPTEMBER 1983 / PAGE 204
The new 600XL and 1400XL computers were exactly what I expected (except that Atari goofed and changed the number on the 1201 XL—and that’s a joke until you study the case designs of the 1200XL and 1400XL). The 800XL was a little bit of a surprise, but kind of a logical step now that I have the benefit of hindsight. The 1450XLD was a pure delight.
I really could envision a 1450XLD doing some nice, small business work. Especially if you put one of the new three-inch hard disk drives (that’s over four megabytes of disk space) into that empty space supposedly designed for a second floppy.
If Atari has any problems at all with the XL line of computers, it may be simply that they are priced too close together. After all, an 800XL is essentially a 600XL with 64K of RAM, and the already announced RAM-pack for the 600XL ends up producing an equivalent machine for the same price. Redundancy.
The 1400XL suffers a little, also. After all, if the rumored price of the 1450XLD holds up ($800–$900 retail), why would you buy a 1400XL and then add a snail’s-pace 1050 drive when you can have the much faster XLD for less money? And who but the more sophisticated user will buy a 1400XL when the 600XL (even with expansion to 64K) is so much less? Will the modem and speech synthesizer really prove attractive to a first-time user? Atari marketing obviously thinks so. I think that people who know they want those features will also know enough to want a disk drive.
Anyway, all of that is crystal-balling and nitpicking on my part. The new lineup of computers is one that any company could be proud of. Atari should be doubly complimented after the fiasco with the 1200.
Before I stop making observations about Atari, though, I would like to carp a bit about one thing: the new Atari disk drives and DOS III (or is it DOS 3?). When I first heard that Atari was going to throw away a potential 50K per disk drive, I thought there was an almost-good excuse. After all, Atari DOS 2.0S could, with absolutely minimum modifications, utilize all the sectors of the one-and-one-third density 1050 drive, so the change, though inefficient when compared to true double-density drives, would allow many current programs to work without modification.
It is not to be. Atari DOS III is just as different from DOS 2.0S as our own Version 4 OS/A+ is. Which means many, many programs (including data base programs, etc.) simply will not work without modification. I do not feel this is inherently bad. Let’s face it: DOS 2.0S is not a particularly good DOS and it is totally inadequate for larger disk drives. DOS III is actually a very nice DOS for small drives (say up to 128K per drive). It goes downhill rapidly when used on larger drives. This means that if you convert your programs and data files from DOS 2.0S to DOS III this year, you will have to convert to some other DOS again next year, when you move to one of those nice little hard disks I mentioned.
Anyway, when the 1050 finally appears,. watch here (I hope) for instructions for using DOS 2.0S (or OS/A+ Version 2) in one-and-one-third density mode, so you won’t have to convert all your programs. (You’ll still have to convert the diskettes themselves, which won’t be easy or fast if you only have one drive, but the same holds true of DOS III—and, to be fair, OS/A+ Version 4—so you won’t have lost anything.)
This month, I will discuss some more techniques which can be used to make your machine language self-relocatable. Last month, we noted which kinds of instructions were implicitly “safe” (register-only instructions, branches, etc.). There was also a list of “Safe Relocatable Techniques.” To summarize, the safe techniques mentioned were:
I also promised to discuss two points this month: (1) where the “safe” locations in Atari memory are; and (2) some special techniques usable only with Atari BASIC. Let me fulfill my promise.
There are none. Next topic.
Oh, all right, I admit that is a bit of an exaggeration, but it is dismayingly close to the truth. When I write machine language routines, I really do prefer that they be usable with as many products as possible. Just as a start—and not as a comprehensive list—I would hope that they would work with the following software: Atari BASIC, Atari DOS, OS/A+, BASIC A+, Atari Microsoft BASIC, AtariWriter, Atari Assembler Editor Cartridge, MAC/65, AMAC, and a few more.
Okay. Not too long a list. How many zero page locations are not used by any of those? None. How many Page Six locations ($600 through $6FF) are not used by any of those? None. How many…. But I think you get the idea. Is all this strictly true?
Actually, there are quite a few bytes which can be used for your temporary storage. And I suggest you consult your Atari Technical User’s Notes or Mapping the Atari (from COMPUTE! Publications) to find where they are. (Caution: Watch out for changes in the new XL computers.) But even these locations are suspect. What happens if I write this neat new printer-spooler routine which uses location $00 (believe it or not, that’s free in almost all the above programs), and-then you come along and add a driver for graphics mode 27 and you use location $00?
Perhaps I am being a bit of a purist here. Certainly very little of my own programming is this clean, this free of conflict with other potential programs. And yet it really does require only a little more work to write a program “correctly” (by my definition), so why not do it right? Let’s try.
So, we must assume that no location outside our own, self relocatable, properly-loaded-at-LOMEM program is safe at all times. Unpleasant. However, that does not say that we can’t use some almost-safe locations while our routine has control. In particular, you should be able to use several reserved locations in zero page (for indirect-Y pointers, etc.) by, if necessary, moving values into them from within your relocatable block; using and/or changing the zero page locations in your program; and then moving the values back into your relocatable block.
Sounds complicated? It is. And yet you might be surprised at how seldom you really need to go through all that.
So what zero page locations are safe, even as temporaries? Probably the safest spots, as long as you aren’t writing an interrupt handler, are those locations used as temporaries by the DOS File Manager. Locations $43 through $49, inclusive, are always reinitialized by FMS every time it gets control. FMS does not presume the locations have maintained their contents from one call to the next. (In fact, the locations should properly be called “Device Driver Zero Page. Temporaries,” since that is what they were intended for.)
And one more comment before I leave you with the impression that absolutely nothing is safe to do on the Atari computers. If you are writing routines specifically designed to be used with Atari BASIC (as I suspect the majority of you are), there are several safe temporaries. First, you can always use the floating point work area, $D4 through $EF, whenever BASIC calls either a USR routine or an I/O routine. Also, BASIC does not use locations $CB through $CF (only four bytes!) at all. Again, let me give you the caution about adding your routine to a system which already has a custom routine. Be sure there is no conflict.
It’s true. There really is such a thing. There are some ifs though: if you are using Atari BASIC or OSS BASIC A+ or OSS BASIC XL; if you have placed your relocatable program in a string and are calling a machine language routine via USR(ADR(STRING$)) or USR(“…machine-language-string…”); if you don’t mind a small trick.
First, the trick. It’s really quite simple. Whenever BASIC calls a USR routine, it calls the routine by placing the routine’s calculated address in location $D4–$D5 (which just happens to be the first two bytes of floating point register zero). It then JSRs to a routine which simply does a “JMP ($D4)”, a jump indirect to the USR routine.
But why can’t we take advantage of that pointer? It already points to our relocatable program, so why can’t it point to our relocatable data? Perhaps a demonstration is in order.
FR0 = $D4 USRROUTINE CLC BCC START ; branches are ok ; SAVEBYTE .BYTE 0 ; some data ; ;begin actual code ; START LDY #SAVEBYTE-USRROUTINE ; index PLA ; count of parameters CMP #1 ; how many? BNE NOPARAMS ; none, we presume ; the user is passing a byte to us PLA ; high byte ... ignored PLA ; low byte ... stored STA (FR0),Y; thusly ; we join here, whether a byte is passed or not NOPARAMS LDA (FR0),Y; get the byte STA FR0 ; to be returned LDA #0 STA FR0+11 ; high byte zero RTS
This program is a very dumb one, for demonstration purposes only. If you call it from BASIC via, for example, “PRINT USR(routine)”, your program will print the byte value saved in location SAVEBYTE (zero, initially). On the other hand, if you use “JUNK=USR(routine, 97)”, the routine will store the second parameter (97) in location SAVEBYTE. Presumably, you could then later recover the 97.
The point to be made, however, is that this program is completely self-relocatable and yet is able to load and store data from within its own relocatable block! The secret is the “LDY #SAVEBYTE-USRROUTINE” line directly after the label START. Since location FR0 contains the address of USRROUTINE, loading the Y-register with the proper offset (SAVEBYTE-USRROUTINE, which happens to be 3 in our example) will allow us to do indirect loads and stores to any location within 255 bytes following USRROUTINE.
Can I put that more clearly? Since, when we do either the “LDA (FR0),Y” or the “STA (FR0),Y”, the Y register contains the value 3 and location FR0 points to the location USRROUTINE, the LDA and STA instructions will reference the third byte after USRROUTINE. Which just happens to be SAVEBYTE.
And just a reminder if you don’t know or remember what the PLA instructions in this program are for. Whenever BASIC calls a USR routine, it pushes all the parameters it is given onto the CPU stack (after first converting them to 16-bit integers, of course). Then, the last thing it does before the call is to push a count of the number of parameters (presumed to be 1 or 0 in our example) onto the same stack. Thus, the first PLA lets us discover how many parameters were passed. The other two PLAs are necessary if a parameter is passed; otherwise the RTS instruction will return to an unknown location and will likely crash the system. (Note that in our simple-minded example you can probably crash BASIC by calling the routine with two parameters, since no check is made for more than one parameter.)
Next month we’re going to take this technique a couple of steps further. We will discover how to have more than 255 bytes of relocatable storage (which may or may not be useful to you) and how to generate similar self-pointers when the routine in question has not been called from BASIC.
COMPUTE! ISSUE 41 / OCTOBER 1983 / PAGE 170
Last month, I said that this month’s column would include the final part of the series on writing self-relocatable code. Unfortunately, that project has turned out to be bigger than I thought it would be, so I am going to put it off a month and devote an entire column to it. However, as compensation, I will finally discuss the “new” Atari graphics modes I hinted at a couple of months back. Before I get to the juicy stuff, though, I’d like to continue a little of the ranting and raving that I started last month.
I heard (from two different sources) the official Atari “line” regarding the new 1050 disk drives. It seems that Atari chose to utilize only 128 bytes per sector and only 127K bytes of file space per drive in order to achieve “increased reliability.” Honest. Do you believe it?
Actually, that’s pure computer puckey (to paraphrase Colonel Sherman Potter). And it’s ridiculous for several reasons.
First off, Atari is implying that double-density drives are unreliable. If that’s true, then IBM, Radio Shack, Commodore, and a lot of other computer companies are in real trouble. Actually, Atari and Apple are the ony major computer companies still relying on single-density technology as their primary modus operandi. And, despite Atari’s claims, even Atari’s 1050 is actually using true double density.
It turns out—based on what we have gleaned from the specs of DOS III at this time—that Atari formats the 1050 drive with 40 tracks of 32 sectors each, with 128 bytes per sector. That’s a total of 160K bytes. Most double-density manufacturers achieve either identically the same total (40 tracks times 16 sectors times 256 bytes) or slightly more (40 tracks times 18 sectors times 256 bytes equals 180K bytes—the format used by most Atari-compatible drives such as Percom, Astral, Micro-Mainframe, etc.). So why does Atari claim only 127K bytes?
Real simple: DOS III only supports 127K bytes. Shall I say that I don’t know why Atari chose this limitation? With a relatively minor modification, and by using only another 64 bytes of memory per drive, DOS III could have supported a full 180K drive.
Now, as it turns out, I do happen to know the real reason Atari chose 128 bytes per sector. And I know this from the most reliable of sources, one of the DOS III’s designers.
It seems that so many of Atari’s own products violate Atari’s own “rules” (especially those about respecting the LOMEM pointers), and so many other products also reach outside DOS to do direct sector disk I/O that Atari’s planners were fearful of the impact of changing either LOMEM or the sector size. Hence the scheme of DOS III.
A secondary impact of the LOMEM problem was that it caused more and more of DOS III to be moved to the diskette from memory, to be called in as overlays when the user requested a function not in memory. Even the keyboard menu processor eventually got moved to disk. The result of all this? While DOS III may be the easiest-to-use DOS yet, it still suffers from the time-consuming swaps to a MEM.SAV file when you want to achieve something as simple as getting a disk directory.
(Of course, there is a very, very elegant way to completely avoid the LOMEM problem on the new Atari XL computers. Why not move the DOS into the as-yet-unused extra memory? Why waste 14K bytes of RAM? I probably shouldn’t drop this idea in Atari’s laps [I should sell it to them], but it will take them at least six months to even discuss it, so I figure it’s OK.)
As I said last month, DOS III contains a nice little file manager. It’s a crying shame that it wasn’t released three or four years ago, since it seems ideally suited to an 810 size drive. But it doesn’t look to me like a system for the long haul, when larger and larger drives become available for the new Atari computers.
And lest too many of my critics cry “foul” for my promoting OSS’s version 4 DOS (which will allow up to 32 megabytes per disk drive), let me hasten to say that I am not suggesting that version 4 and the 1050 are necessarily the answer. What I am saying is that Atari could have achieved virtually the same results by sticking with DOS 2.0 and extending it to handle up to 120K bytes of file space (with 128 byte sectors—it will handle 240K bytes with 256 byte sectors).
Well, enough. I promise no more on this subject until I give you the patches to DOS 2.0s to give you 120K bytes on a 1050. In the meantime, ask yourself this question: if DOS III is limited to 127K bytes of file space, how will Atari handle the double-sided, double-density drive in the 1450XLD, which will have a capacity of at least 320K bytes? Atari, will you answer?
One more comment. I just want to say that, aside from the 1050, I am impressed with all of Atari’s new hardware products. And I even like some of their new software. I think Atari is back on its feet and running hard.
Many of the games currently on the Atari market use custom-designed character sets for background displays. The classic example of this is, of course, Eastern Front by Chris Crawford. That beautiful scrolling map he displays is actually composed of “characters.” This works because a couple of the ANTIC graphic modes allow the programmer to treat each pair of bits within a character cell as one of four colors.
In fact, by controlling the high order bit of the character to be displayed, the programmer may choose two different sets of four colors. Which would be really nice except for the fact that only one of the colors can change between the two sets, thus there is a total of five displayable colors.
If you don’t remember and/or understand all that, don’t worry. There’s a better way. A way which will get you seven colors! The method only works on machines with a GTIA installed, but I hope that all COMPUTE! readers have added a GTIA by now. (If you have purchased a machine in the last year and a half or so, you got a GTIA with your machine. If you have an old machine with a CTIA, the upgrade cost is nominal.)
The credit for finding and documenting this until now hidden feature of the Atari must go to Steve Lawrow, the author of our MAC/65 assembler. He did a nice job of investigating all the ramifications and provided me with the table which I’ve reproduced here. Before I go into the details of the table, though, let me briefly describe how one accesses two new Atari Graphics modes.
First, the new modes are variations on BASIC GRAPHICS 1 and GRAPHICS 2 (and, by extension, GR. 17 and GR. 18). And the method of producing the variations is so simple that it’s almost funny that no one stumbled on it before. Simply turn on the GTIA’s special color mapping mode. And what, pray tell, is that? In this case, it is the upper bit of GPRIOR, the priority select register.
GPRIOR is a hardware register that has its OS shadow location at $026F (decimal address 623). That means (for those of you not familiar with shadow locations) that by changing the RAM location $026F you cause the OS to change the appropriate hardware register for you. (And see COMPUTE!’s book Mapping the Atari if you need to know more.)
Briefly, then, you need simply to turn on the upper bit of GPRIOR in order to activate these new modes. There are, however, some caveats to be observed. Perhaps the easiest way to observe the toughest potential problem is to turn on your Atari, go into BASIC, and do a POKE 623,128.
What do you see? Garbage on the screen, if you have a GTIA. Unfortunately, activating the GTIA destroys the normal character display mode(s). More on this later.
Now, on to the table. When you tell BASIC to PRINT #6 in Graphics modes 1 and 2, it prints larger than normal characters to the upper portion of the screen. In particular, though, the characters are available in several different colors. Try this little program to see what I mean: GRAPHICS 2 : PRINT #6;"AaAa" (where the underlined characters are typed in inverse video).
And why do you get four different colors? Because the upper three bits of each of the characters are different. In particular, the upper three bits for the four characters shown are 010, 011, 110, and 111, respectively. Because you are in Graphics mode 2, all four characters came out as uppercase letters.
Now, the bytes which are put in screen memory are actually translations of the bytes which you PRINTed. In particular, when the bytes shown are translated to screen codes, they end up with upper bits of 00, 01, 10, and 11, respectively. The upper two bits of the bytes placed in screen memory determine the color to display; the bits in the character set determine which bits will be “turned on” on the screen.
The concept used in our “new” graphics mode is similar. In particular, the upper two bits of the bytes placed in screen memory determine the color MAP to use. The actual bits in the character set determine which color will be selected from the appropriate map. In other words, we have added yet another level of color indirection to the Atari!
In GRAPHICS 10, memory is organized in groups of four bits. The value of the four-bit nybbles determines which color register is displayed. Thus, since there are nine color registers (five for the primary graphics and four for player/missile graphics), there are a maximum of nine independently displayable colors. (Yes, I know that you can get 16 colors in GRAPHICS 9 and 11; but in those cases the colors are not truly independent.)
In GRAPHICS 1+ and 2+ (well, I had to call them something, didn’t I?), pairs of bits (instead of four-bit nybbles) determine the color register to use. Remember, though, that the pair of bits can only select a color from the particular MAP which has been selected by the two upper bits of the character on the screen.
And, finally, this implies that the other six bits of the character on the screen select the particular character from the character set memory, just as normal GRAPHICS 1 and 2 do.
Does that sound complicated? It should, because it is. Anyway, now is the time to look at the table. It shows the MAPs that are available.
Color Selection Under GRAPHICS 1+ And 2+
Bit Pairs Of Color Selectors In Character Memory
00 01 10 11 00 704 704 704 704 01 704 705 712 709 10 704 706 712 712 11 704 707 712 711
And, you presumably ask, what are the numbers shown in the table? Simply the location of the color register which will be displayed when you use the given bits within the given map. For example, 704 is PMCOL0 (player color 0) and 712 is PFCOL5 (playfield color 5). However, the easiest way to change the color registers, in this instance, might be to refer to them via the locations shown in the table.
So, writing POKE 704,0 will make the background color black. Writing POKE 712,152 will give you a nice blue for bit pattern 10 in MAPs 01, 10, and 11. A little observation of the table will show you that MAP 00 is essentially useless: it always gives you the background color, regardless of the bit patterns in the character memory.
On the other hand, bit pattern 00 always gives you background color, regardless of the MAP used, so it may prove useful in many circumstances. For the rest, note that MAP 10 gives you only three colors, but it is the only MAP which gives color 2 (706). Sigh. The system is not totally flexible, but it is handy.
First thing next month we’ll put this all together with a little BASIC program that demonstrates the capabilities of the new modes.
COMPUTE! ISSUE 42 / NOVEMBER 1983 / PAGE 207
Bill concludes last month’s column with a program demonstrating the capabilities of the new graphics modes.
If you were a little disconcerted by our discussion last month, here is a little BASIC program which demonstrates the capabilities of the new modes in a crude, but visible, fashion. As usual, I will explain the program line by line.
120. Selects a normal GRAPHICS 2. This is our starting point.
130. Prints a reference line on the screen. This is simply so you can tell where the columns of characters are later, when they get MAPped.
150–180. Print what are now normal characters. Note that the underline denotes inverse video characters (via the Atari key). Did you notice that each set of four characters here will produce the MAP patterns 00, 01, 10, and 11 (in that order) on each line of the displayed area? Remember that the other six bits, then, will select a character from character memory.
190, 290, 320, and 340. Just messages, to tell you what we are doing.
200–220. We are moving the normal Atari 800 character set from its normal location ($E000) to RAM at address $6000. Note: This requires a 32K machine.
230–250. Here we read the DATA statements from lines 380 to 420 and change the character set for the characters A, B, C, and D.
260–280. A quick and dirty way to arbitrarily select some colors for the various color registers.
300 and 330. Just some delay loops, so you can actually see it happening.
310. Changes the CHBASE (CHaracter BASE pointer) to point to location $6000, where the new character set pattern is.
350. The magic instruction. Look at your screen. How many different colors do you see?
Do you see the relation between the display and the table? Did you notice that the first character in each line “disappeared”? That’s because these characters are using MAP 00, the “all background” map.
I think the only thing left is to explain the bit patterns of the modified characters which are read in by lines 230 to 250.
Character A is changed to a solid block of all “11” bits (thus the pattern is eight $FF bytes).
Character B is changed to a solid block of all “10” bits (eight bytes of $AA). Character C is a solid block of “01” bits (eight bytes of $55).
Finally, character D has a purposely varied pattern. The bit patterns in the byte are as follows:
228 $E4 11 10 01 00 57 $39 00 11 10 01 78 $4E 01 00 11 10 147 $93 10 01 00 11
and then the same bytes in reverse order.
The result of the shifted bit pattern shown is, quite naturally, the “arrows” which you see in the program’s display.
Finally, we are finished explaining these new modes. What good are they? Just imagine what Chris Crawford could do with a map which displays seven different colors, instead of only four. But surely there are other uses. How about inventing some and sharing them with us?
100 REM DEMO OF THE "NEW" GRAPHICS MODE 1 110 REM 120 GRAPHICS 2 130 PRINT #6;"wxyz" 140 PRINT #6;" 150 PRINT #6;"AaAa" 160 PRINT #6;"BbBb" 170 PRINT #6;"CcCc" 180 PRINT #6;"DdDd" 190 PRINT "THIS IS IN NORMAL GRAPHICS 2" 200 FOR A=24576 TO 25599 210 POKE A,PEEK(A+32768) 220 NEXT A 230 FOR A=24840 TO 28671 240 READ D:IF D<0 THEN 260 250 POKE A,D:NEXT A 260 FOR A=0 TO 8 270 POKE 704+A,18*A+18 280 NEXT A 290 PRINT "THIS IS WITH COLORS CHANGED" 300 FOR I=1 TO 1000:NEXT I 310 POKE 756,96 320 PRINT "THIS IS THE MODIFIED CHARACTER SET11 330 FOR I=1 TO 1000:NEXT I 340 PRINT "FINALLY, THE NEW AND SPECIAL MODE!" 350 POKE 623,128 360 REM == JUST A LOOP TO KEEP DISPLAYING == 370 GOTO 360 380 DATA 255,255,255,255,255,255,255,255 390 DATA 170,170,170,170,170,170,170,170 400 DATA 85,85,85,85,85,85,85,85 410 DATA 228,57,78,147,147,78,57,228 420 DATA -1
COMPUTE! ISSUE 43 / DECEMBER 1983 / PAGE 264
This month I will discuss extended memory management on the Atari computers. Before I start, though, I would like just to chat for a bit. (If you are waiting for the last part of the series on self-relocatable code, be patient. It’s just bigger than I expected it to be, so I’ve got to massage it a bit more.)
Today I read an interview with Alan Kay in Technology Illustrated. As many of you probably know, Alan Kay was perhaps the most instrumental person in the development of the Smalltalk language. (Or is it an operating system? Or is it more properly called simply an “environment”?)
The work he did on Smalltalk while at Xerox caused him to believe that computers were destined to become a household tool, as common as, say, the television set. (Which may seem a mundane belief today, but Kay was saying such things five to ten years ago.) Well, Atari apparently liked Kay’s philosophy, vision, and capabilities, and hired him awhile back.
The article I read interested me in two ways. First, it labeled Kay “Atari’s Chief of Games.” Well, I had been led to believe that he had been brought to Atari to head research and development, presumably to lead Atari into the generation beyond Smalltalk (a logical presumption, since he’d stated that he felt Smalltalk had served its purpose, was obsolete, etc.).
Anyway, with my orientation toward languages and systems, I saw “Chief of Games” as a step downward. Yet the interview made it clear that Kay felt he was in perhaps one of the most challenging positions possible. Hmmm. What has changed? Are games truly the most useful purpose of a computer right now? The marketplace certainly seems to think so. It is food for thought.
The second thing in the article which really got my CPU stirred up was Kay’s view of the computer. I had always been under the impression that he believed his real goal in life was to enable everyone not only to use the computer, but to actually command and manipulate it. (I hesitate to say “program it,” but then Smalltalk is a language.) In the interview, though, Kay stated he was beginning to fear that perhaps the computer was not so much a household tool as it was a fine instrument, like a violin. He strengthened the analogy by noting that very few people can play the violin, just as very few people can properly use a computer.
Well, I for one believe that not only is the analogy inappropriate, but its projection of gloom and pessimism about the future of computers is not justified. Granted, the analogy may hold today. After all, only about 1 percent of the United States population can claim to be able to program at all (or play “Twinkle, Twinkle, Little Star” on the violin). Probably less than .1 percent produce acceptable application programs (or play in a community orchestra or equivalent). Dare we guess that .01 percent are commercial programmers (or make their living playing the violin)? Can it be that only .001 percent can actually write systems and languages (or are the guest soloists of the concert world)?
Actually, these proportions are just order-of-magnitude guesses, but they do seem to support Mr. Kay’s analogy. But I say that his analogy has validity mainly because the computer is still such a relatively “rare” instrument. Personally, I prefer a different analogy.
When computers are as much a part of everyday life in this country as automobiles are now (and I firmly believe that they will be), then I think they will be treated much as automobiles are.
Let me sidetrack a little. Here in California, the State has decreed that all high school students shall take a course in “computer literacy.” So what happens? Every high school is scrambling to buy one or two computers and begin teaching every kid how to program in BASIC. Great, right? Nonsense!
First of all, I can’t conceive of learning how to use or program a computer at all if the student/computer ratio is above 3 to 1. More importantly, I think it is senseless to equate “computer literacy” with “learning to program in BASIC.” After all, “automobile literacy” consists of learning traffic laws, safe driving techniques, and actually starting to drive a car (it’s usually called “Driver Training”).
“Automobile expertise,” on the other hand, consists of learning what tools do what, the theory and practice of internal combustion engines, and how to maintain and repair an automobile (and this is usually called “Auto Shop”). Does every student take driver training? Yes, or nearly so. Does every student take auto shop? No. Not by a long shot.
So, I believe, it should be with computer literacy. Don’t teach everyone how to program. (What would we do with a nation of programmers? The same thing we would do with a nation of auto mechanics?) Instead, teach everyone how to use a computer to do word processing, to balance their budget, to access data bases, and the list could be quite long.
And, yes, keep the computer programming classes. But keep them on the same basis that auto shop classes are offered—as electives, for those interested in learning more than how to “drive” their computers or cars.
Why this confusion of computer literacy and computer expertise among schools and teachers? Partly because the computer industry has promoted the view. (Perhaps fearing that current applications programs are inadequate to a classroom situation?) Partly because of a dismal lack of education and information on the part of the educators. (Pity the poor math or history teacher who is nearing retirement. Suddenly he/she is forced to learn enough about these nasty machines to be able to teach some kids how to use it. Do you wonder that the path of least resistance is most often chosen?) Mostly, I suppose, because BASIC comes built into each machine, while good text processors, spreadsheet programs, etc., cost extra, money which most schools don’t have.
So how does this tirade relate to either Alan Kay or you, my patient reader? Well, first of all, I think the analogy of car and computer is a better one than violin and computer. And, perhaps, if computer companies started trying to design mass consumable “cars” instead of trying to ply the public with precision instruments, it is a future that will come true. To be fair, I think that companies such as Atari and Commodore and Apple and others are starting to do so already. But my cynicism leads me to believe that they are driven by the current market, not by the future one.
Perhaps more importantly, though, I am trying to convey the message that those of you who read this column (and this magazine) are, in some sense, ahead of your time. You are, indeed, the violinists that Alan Kay perceives. Some of you are just learning to play your first notes. Others of you are already tackling the great concertos. But, when the computer revolution really arrives, you will all have the advantage of having already taken at least your first “auto shop” course. So, if you enjoy your computer (and particularly if you enjoy programming), don’t give it up easily. And certainly don’t give it up now. Someday, others will appreciate your art, however humble or glorious it may be.
Did that sound like a sermon? If so, I apologize. But it’s my view of both the present and the future of computers and programming. One last sidelight before we move on: On hearing me espouse the views above, someone once asked me what my position in the hierarchy was, as a person who helped design (as opposed to program) operating systems and first languages for new machines. Actually, that’s an easy question: I’m simply a composer. And so, I think, are such people as Alan Kay.
All of the new Atari XL computers (including the 1200XL) will contain 64K bytes of RAM (the 600XL requires an external RAM pack to do so). And all contain 16K bytes of Operating System ROM space. And, further, all (except the 1200XL) include good old Atari 8K BASIC. Let’s see here— 64K plus 16K plus 8K— that’s over 90,000 bytes of space.
Wait a minute, though. If I plug in a 16K cartridge (such as AtariWriter or ACTION! or BASIC XL), then I could have 104K bytes of RAM and ROM. Wow. That’s really nifty, right? Well…
Have you read this column often enough to know that “Well…” means “not really” or “there’s more to come”? No? Well…
Not really. To begin with, all Atari computers are built around the same CPU (Central Processing Unit), the 6502. (Which, incidentally, is the same chip used in most Commodore computers and all Apple machines except the Lisa.) However, there is a fundamental restriction involved when using a 6502: There is simply no way to access more than 64K bytes (65,536 bytes) at one time. How, then, can the Atari use 104K bytes? Is someone fibbing to us?
The key here is the phrase “at one time.” A juggler may be able to juggle only four things at a time. Does that mean he always juggles the same four objects? Should we presume that the 6502 must always work with the same 64K bytes? Of course not.
In point of fact, the new XL machines allow the 6502 a number of choices about which bytes it will “juggle.” How the 6502 makes its choice is the subject of this section.
Actually, there is no magic formula or scheme which enables the various choices. In fact, various choices are made by differing means. Generally, the choice is “consciously” made by the program currently in control of the machine. And it makes the choice simply by (usually) storing something in a particular memory location. Confused? Let’s digress a little.
Some CPUs (including microcomputers and minis and maxis) treat input/output as a separate domain from general memory. For example, the 8080/Z-80 group of processors allow up to 256 separate input and output ports, which are completely separated from the general RAM/ROM memory (they even have special instructions specifically for reading/writing these I/O ports). On the other hand, many machines (such as the 6800, 68000, and 6502 families, as well as such giants as the PDP-11 series) simply treat input/output ports as part of the general machine memory.
The advantages and disadvantages of each scheme are a subject of hot debate, but I will only present a single aspect of each here: Keeping the I/O ports out of general memory allows a true 64K bytes of RAM when using an 8- or 16-bit microprocessor. Allowing I/O to be treated as part of memory means that any instruction which can access RAM or ROM can also access a port, often resulting in efficient and easy-to-learn coding.
Anyway, note that the 6502 does, indeed, use what is called “memory mapped I/O,” and Atari computers do, as a consequence, reserve 2K bytes of memory (addressed from $D000 to $D7FF) which is specifically designed for I/O port addresses. (If losing 2K of your space seems excessive, pity the Apple owner who loses 4K.)
In the case of the XL machines, then, one simply changes the value in an I/O port—which appears to one’s program as a memory address—and presto, a different choice of “jugglable” memory is made. But what I/O port to use? Did you notice the fact that Atari 400 and 800 computers have four joystick ports while the XL machines have only two? Guess which ports are now used for memory juggling. Did you need more than one guess?
For the more hardware-oriented of you out there, I will note that all four Atari joystick ports are actually nibble-sized pieces of a 6820 (or 6520) PIA (Peripheral Interface Adapter). The PIA is a very flexible chip; it allows each of its 16 I/O pins to be separately configured to be either an Input line or an Output line. In the case of the 400 and 800, all 16 lines are configured as Input, since they are all used to read the four directional switches of an Atari joystick. In the case of the XL machines, some of them have been changed to Output lines, thus enabling them to act as electronic switches.
On the 1200XL, for example, two of them are used to control the L1 and L2 status LEDs. And (you saw this coming, I presume) two of them choose certain configurations of the computer’s memory. (On the other XL machines, still another line is used to control still another possible configuration.)
Since we are discussing memory configuration choices, I might as well confuse the issue a bit more by also mentioning how we at OSS implemented our new SuperCartridges. It is probably no accident that Atari provides the cartridge slot on all machines with a line labeled “CARCTL”, an abbreviation for CARtridge ConTroL. Actually, this line is active whenever any memory location from $D500 to $D5FF is accessed. Since no Atari cartridges take advantage of this line, we thought it was time that we did so.
About now, it is past time for a diagram. The figure shows all the possible choices of memory configuration by placing them in memory address order. Note, though, that the 64K addressing restriction of the 6502 applies. Hence, when two or more choices are given for a particular address range in memory, remember that only one such choice may be active at any given time. For each address range where a choice is available, there are two or more banks of memory. And choosing one bank over another is called bank switching or bank selection.
For example, I might choose to use BANK1 of the SuperCartridge while at the same time choosing the RAM BANK of system memory. The important thing to note here is that each set of banks (that is, parallel memory segments), as shown in the figure, is independently bank selectable.
Also, some bank choices are not available at the software level. For example, when you plug in a Microsoft BASIC cartridge, you have 16K bytes of ROM from $8000 to $BFFF. You have no RAM in that address range. You have no choice in the matter. This is, then, hardware bank selection.
The advantage of hardware bank selection is that it is essentially foolproof. If the hardware removes a bank of RAM from your program’s “vision,” your program can’t get into trouble trying to use that bank.
But the advantage of software-selectable banks is, quite simply, that they allow you to expand the capabilities of your machine. If you look at the figure, you can see that a SuperCartridge allows you 16K bytes of programming power while occupying only two 4K byte banks at any given time.
Memory Map Of Atari XL Computers (Showing Parallel Memory Banks At Same Addresses) FFFF ┌───────────────┐ ┌───────────────┐ │ │ │ 10K │ │ │ │ ROM │ │ │ │ │ │ OS │ │ │ F000 ├───────────────┤ │ │ │ ROM │ │ │ │ │ │ │ │ │ │ (Selected by │ │ │ │ "turning off" │ E400 ├───────────────┤ │ OS ROM │ │ Character Set │ │ space— │ │ ROM │ │ uses PIA port)│ E000 ├───────────────┤ │ │ │ │ │ │ │ Floating │ │ │ │ Point ROM │ │ │ │ │ │ │ D800 ├───────────────┤ └───────────────┘ │ │ ⁝ ⁝ │ Input/Output │ ⁝ ⁝ Always (de)selected │ Ports │ ⁝ ⁝ together │ │ ⁝ ⁝ D000 ├───────────────┤ ┌───────────────┐ │ │ │ │ │ OS │ │ │ │ ROM │ │ │ │ │ │ │ C400 ├───────────────┤ │ │ │ International │ │ │ │ Character Set │ │ │ OSS SuperCartridge C000 ├───────────────┤ ┌───────┴───────┬───────┼───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │ │ 8K │ │ 8K │ │ 16K │ │ 4K │ │ │ │ RAM │ │ ROM │ │ ROM │ │ ROM │ │ │ │ │ │ Cartridge │ │ Cartridge │ │ │ │ 8K │ │ │ │ │ │ │ │ │ │ Atari │ │ │ │ │ │ │ │ │ │ BASIC │ │ │ │ │ │ │ ├───────┬───────┼───────┐ │ ROM │ │ (Selected by │ │(Automatically │ │(Automatically │ │ 4K │ 4K │ 4K │ │ │ │ "turing off" │ │ deselects │ │ deselects │ │ ROM │ ROM │ ROM │ │ (Not present │ │ Atari BASIC— │ │ RAM and │ │ RAM and │ │ │ │ │ │ in 1200XL) │ │ uses PIA Port)│ │ Atari BASIC) │ │ Atari BASIC) │ │ Bank1 │ Bank2 │ Bank3 │ │ │ │ │ │ │ │ │ │ │ │ │ A000 ├───────────────┤ └───────────────┘ └───────────────┘ │ │ └───────┴───────┴───────┘ │ │ │ │ │ │ │ │ │ 8K │ │ │ 9000 ├───────────────┤ │ │ │ RAM │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 8000 ├───────────────┤ 57FF ┌───────────────┐ └───────────────┘ │ │ │ Diagnostic │ │ 32K │ │ ROM │ │ RAM │ 5000 └───────────────┘ │ │ │ │ │ │ 0000 └───────────────┘
And the purpose of this discussion? To show that the XL machines really do have a lot of latent power. How do we make it un-latent? Well.…
As I write this article, the number of commercially available programs which allow you to take advantage of the extra 14K bytes of RAM on an XL machine is countable on the fingers of my left foot. Zero. By the time you read this, there will likely be products heading your way that will justify the purchase of an XL machine (or a 64K memory board, such as the one from Mosaic Electronics, for your 800).
Since I am obviously most familiar with DOS XL, let me explain a little of how it works.
When DOS XL boots into an XL computer, it first establishes a set of jump vectors for the various interrupt routines. Why? Because any IRQ, NMI, or SYSTEM RESET will attempt to jump through the vectors which must (by 6502 CPU law) be located at addresses $FFFA through $FFFF. If we deselect the OS ROM bank in order to enable the RAM bank at the same addresses, the contents of these critical addresses are unpredictable. We must supply some valid routine addresses or the system will crash.
DOS XL puts most of the DOS code in the RAM bank which is “under” the OS ROMs. It also leaves a piece of itself at the conventional DOS load address of $700 (an area of memory which is not bank selectable). Then, if there is a BASIC cartridge in the machine, it selects the OS ROM bank and jumps to BASIC.
So long as BASIC makes no calls on DOS, all is calm and expected. However, watch what happens when (for example) we try to open a file from BASIC.
Wasn’t that fun? For even more fun, try to trace what happens if interrupts occur during any or all of the above steps.
But why do we go through all this? Because, even though Atari saw fit to include all this good memory bank selection capability, they provided no software to use it. So why not just forget the bank select and pretend we are running on an Atari 800 or 400? Because the net gain to you, the BASIC or ACTION! or Assembler or whatever user, is about 5,000 bytes of user space. Your programs can be 5K bytes bigger. Your spreadsheets can contain many more cells. You can edit more text.
Of course, some programs (such as VisiCalc) which do not use a standard DOS or which use a heavily protected disk (such as the Microsoft BASIC extensions) will not be able to take advantage of the extra memory. But they, too, can use these techniques to extend their capabilities if the software companies producing them will decide that the XL machines are worth the little extra effort.