The Atari Wedge: Adding Commands To Atari BASIC
You can customize your Atari BASIC by adding new commands to the language itself. To demonstrate how to do it, the program below adds five DOS commands to BASIC – including a directory command. There are two versions of the same program. Program 1 is a BASIC loader. You type it in normally and it will create a machine language program for you from the information in the DATA statements. Program 2 is a disassembly of the same routine. It shows how the machine language works and is useful to programmers who know machine language or want to learn more about it. It's not necessary, however, to understand Program 2 in order to make good use of Program 1.
A letter was published recently in COMPUTES's "Ask The Readers" column, regretting the need for "this POKE or that POKE" to accomplish various tasks. The required solution is an "expanded command set." An enticing prospect, adding commands to a language, and a seemingly impossible one, too.
Atari BASIC, like most microcomputer BASICs, is "burned" into nonvolatile ROM memory. The machine language routines to list, save, edit, and run your program cannot be altered or "patched" in any way. (However, on a 48K Atari, you can copy the BASIC cartridge to disk as a binary file, modify it with a "machine language monitor," and load it into the top of memory where it will act almost as a ROM cartridge.)
The most common (and easiest to implement) extension of a language is the addition of "immediate mode" commands. These direct commands, which are not usually executed in a program, but from the keyboard, include RUN, SAVE, LIST, NEW, DOS, etc. Thanks to Atari's modular Operating System (OS), we can easily add this type of command.
An Overview Of Atari's Operating System
To understand how the Atari Wedge works, we'll have to delve into the mysterious 10K ROM. If you just want to use the program and aren't concerned about the technical details, feel free to skip ahead. The Operating System (OS) of a computer is responsible for all input and output to and from disk, cassette, printer, and keyboard. It can also perform such chores as memory management and screen display. On many microcomputers, the OS does not exist as a separate entity, but is incorporated into the BASIC interpreter.
The Atari, on the other hand, is the first microcomputer with a general-purpose "plug-in" operating system. This goes hand in hand with the use of program and game cartridges. All programs running on an Atari use a common set of routines, from floating point arithmetic to high-resolution graphics routines such as PLOT, DRAWTO, and FILL.
So, instead of BASIC providing a marginal operating system (which on many machines is a maze of machine language calls, requiring incompatible register setup and initialization), we have a BASIC cartridge which uses "universal" OS routines. A good OS simulates a mini-language. It provides documented, unchanging (between various revisions), unified subroutines with full parameter passing and error-checking.
Furthermore, a good OS is extensible. All the major routines and subroutines are accessed indirectly, through pointers. That is why the Atari is so flexible. If you want to change the personality of your computer, just change one of the vectors of a given routine to point to your machine language routine. Your program can then pass on control to the default program.
A Flexible Computer
This indirection is visible throughout the Atari. At the low end is color indirection, where you can change the color of anything drawn to another color merely by changing one color register. The default character set pointer can be changed to point to a user-designed character set. The system interrupt routines and display list interrupts are all fully accessible via a table of pointers. The BREAK key can be masked; the keyboard scan routine can be modified or bypassed; exotic peripherals can be serviced. And all input/output devices are userdefinable, from the keyboard to the disk drive.
A notable peculiarity of the Atari is that not just the disk drive or printer, but also the TV screen and keyboard, are considered "peripherals." You don't print a character to the screen on the Atari; you send a character or buffer to the Editor device.
Chain Of Command
Through the hierarchy of a subset of the OS, the CIO (Central Input/Output), BASIC politely requests a line of input from screen and keyboard. After BASIC makes this request, control is passed to CIO, which calls the Editor. The Editor lets the user enter a "line" of text (which can be up to three screen lines long). The user can use cursor controls to edit the line or to move the cursor anywhere on the screen to edit another line.
When RETURN is pressed, the line the cursor is on is placed into a buffer (block of memory). Next, CIO gives this information to the calling routine via another buffer. The CIO is designed to be easy to use from machine language. If you think it sounds complicated, imagine performing all these tasks without an operating system!
Driving A Wedge
We don't have to modify BASIC at all. We just "wedge" our way into the Editor device, "E:". As intimated, even the "system" devices such as "E:" or "D:", the disk "driver," can be replaced. Usually, however, you don't want to replace a vectored routine; you just want to insert an additional task. In this case, you point the vector to your routine, which performs the little extra task and then calls the main routine. This "bypass" explains the term wedge.
Figure 1. Wedging Into a Vector
The Handler table contains the names of all the devices. If you wanted to, you could change the name of the cassette device (C:) to another character, such as I : (for Tape), by finding the "C" in the table and changing it to a "T". Along with each name, the Handler table includes an address that points to another table of addresses that point to all the functions of that particular device. This is multi-level indirection. There is even a vector that points to a list of vectors!
We want to modify the Editor, so we change the first vector to point to our list of vectors. All we really need to do is change one of the vectors in the Editor's list of vectors, the "Get Character" address. Since this list is in ROM, at $E4()0, we need to copy this 16-byte table to RAM, modify it, and re-point the Handler table to our RAM version of the Editor Handler table.
A Monitor Monarchy
Now that we've got the Operating System calling our routine instead of the Editor in ROM, we've got total control of almost all console input/output. The Get Character routine, instead of calling E:, asks us for an ASCII character, presumably from the screen and keyboard. We comply by calling the default routine in ROM.
This seems rather roundabout, doesn't it? Bui we reserve the right to monitor all characters returned to the Operating system, and hence, BASIC We get to examine every line of input before that line is returned to BASIC, where any strange new commands would be scorned with an error message.
So, we just catch the carriage return code and leisurely examine the input buffer, located at $0580. All we have to do is compare it against a table of commands, and, if we find a match, execute the command. If not, we just return the line to CIO (and CIO gives it back to BASIC) on the assumption that it's either a blank line, a BASIC command, or a syntax error. Sounds simple, but such a "parsing routine is quite a headache to code and understand.
A REMarkable Solution
After we've intercepted and executed the line, how do we prevent a syntax error when we return the line to BASIC? (And since we've "cut in," we have to follow protocol and return something.) One solution would be to erase the buffer by filling it with spaces. An easier trick would be to change the f character of the line to a period, e.g., "SCRAl Cr D:TEMP' would become ".CRATCH D:TEMP" Since BASIC interprets a leading period as an abbreviation for "REM" (don't ask me why, it's just a lucky fluke), BASIC conveniently ignores the command and returns READY (which it wouldn't if we merely blanked out the line).
The parser routine makes it easy for you to add commands. Just place the name of each command, followed by a zero, and the address where you want control to be transferred after the command is recognized, in COMTBL (COMmand TaBLe, see Program 2). The length of the line is found in LENGTH, and the second character after the command is returned in PARMS (since this is where any parameters would be).
Note that the length is one character past the end of the string, assuming you number from zero. Your command processor can find the command string in LBUFF ($0580).
Theoretically, this technique can be used to add commands to any language environment. You only have to find a way to make the language processor ignore commands when you return the line (such as blanking it out). Of course, the commands themselves are usually language-specific.
Now the way is open to add a plethora of BASIC utility commands. Of course, these will have to be written in machine language and interfaced with the Wedge. I've included the resident DOS commands LOCK, UNLOCK, RENAME, and SCRATCH, as well as DIR to print the directory.
You can study the assembly listing (Program 2). If you have an assembler, try typing it in and modifying it. It contains a wealth of techniques and information, such as pattern matching, indirect subroutine calls, making a Routine "RESET-proof," using CIO for input/output from machine language, long branching, modular programming, calling BASIC'S ERROR routine, even "pressing" SYSTEM RESET from within a program.
Using Wedge 1.0
A machine language program can be hard to even enter into the Atari without an assembler. Program 1 will write the machine language to disk in the form of an "AUTORUN.SYS" file. Save this program so you can write copies to any disk. When you boot this disk, the AUTORUN file will automatically load and initialize the Wedge. You can use the Wedge's "console DOS" directly, without waiting for the disk utility package (DUP.SYS) to load in, and without losing any programs in memory.
Commands provided are DIR (lists the directory of drive one), LOCK, UNLOCK, SCRATCH (delete), and RENAME. Remember to include the D: (or D2: for drive two, if you have one) in the filename with all the commands except DIR. With RENAME, use the convention RENAME D:-oldname,newname".
The Wedge is "persistent"; in other words, it re-initializes itself when you press SYSTEM RESET, so it's kind of hard to get rid of it. An additional command, KILL, removes the Wedge. You can bring back the Wedge with: PRINT USR(7936)..
These commands are just a start. Many others are possible: RENUMBER, FIND, AUTO line number, UPDATE (removes unused variables from the variable name table), and more. If you come up with a useful BASIC utility in machine language, send it to COMPUTE! to be incorporated into a future version of the Wedge.
We've managed to intercept BASIC at the command level. In future issues, we'll go into how you can tell BASIC what to do from machine language. We'll even try to pursue that elusive aim – actually adding commands to a running program.