Supercharging BASIC
by Bob Stewart
You probably noticed that BASIC is sometimes a little bit slow. The Assembler Editor cartridge can help speed up your programs, but programming completely in Assembly Language is a real drag. This article shows you how to write most of your program in good-old BASIC, and only the parts that really need it in Assembly Language.
The Assembler Editor cartridge is a fine tool for adding Assembly Language to your BASIC programs. If you haven't read Appendix 9 of the Manual for a while, give it another look. It contains some necessary information that I won't repeat here. What I'm going to do is pass along a program called Charger, a couple of Assembly Language subroutines, and some techniques, all of which I use in my own ATARI programming work. I assume you already have the Assembler Editor (or equivalent), some knowledge of Assembly Language, and a fair knowledge of BASIC.
The Charger program is written in BASIC. It reads an object file, as output by the Assembler, and converts it to lines you can include in your BASIC program.
The subroutines provided are the two that I use most often. One is a byte mover, good for things like copying a character set from ROM to RAM, or placing a Player /Missile. The other is a byte sprayer, good for changing a chunk of memory.
Additionally, I'll describe three techniques I use for installing Assembly Language routines in my BASIC programs. The first conserves the most space by putting the code in a constant. The second keeps the code in a string. The third uses Page Six RAM.
THE PROBLEMS
The first problem is how to get the Assembly Language into the BASIC program. The second is where to put the code when you get it there.
The technique most magazine articles use to put Assembly code in BASIC is to use lots of numbers in DATA statements with READs and POKEs in a FOR-NEXT loop. This is bulky (two to four bytes of DATA per byte of assembly code) and not very fast. I prefer the ATARI's ability to treat almost every binary byte from 0 to 255 as a typeable, displayable character. So, I put my Assembly code into an ordinary BASIC string. There is just one little difficulty with this. There are two values, the quotation mark (ATASCII value 34) and the end-of-line (ATASCII 155), that cannot be put between the quotes that delimit a string. This matter is also resolved.
The Charger program reads an Assembler object output file and converts it to lines of BASIC code on you screen. You can then load your pro gram and edit the lines into the forn necessary for the storage technique you want to use. Obviously, this means that Charger won't work for Assembly modules that result in more code than will fit on the screen. I have never found this to be a problem because I use only relatively small Assembly modules with BASIC.
The program handles the quote and end-of-line characters by tucking them in separately after it sets up the rest of the string. This special treatment costs some space and inconvenience, but I've found that these two characters rarely occur. When they have appeared, a minor code or data change usually made them go away. But, if you're stuck with them, Charger still works fine. The program also works with object output that skips forward because you used the * = directive more than once. The only restriction is that the code segments must be in ascending order.
THE PROGRAM
Now we'll look at Charger. First, a few notes on programming style. Since this program has no memory problems, and since I intended it to be readable, I kept most statements on separate lines. I did something that's not a good idea if you ever plan to shrink a program by removing the REMarks. In several places I did GOTO a REMark. The best technique in the program is breaking code into logical sections with the line numbering. This makes BASIC programs easier to write and modify.
Lines 1000-1040 are initialization. FILE$ and TEMP$ are for the file name input code. BLOCK$ is for keeping track of undisplayable characters. Q$ is simply a quote character to make the PRINTing of code lines look cleaner. BEGIN is to become the beginning code address for a later error-check, here I initialize it to a recognizably invalid value.
Lines 1100-1160 get a file name. If the input is empty, I default the name to one I often use.
Lines 1200-1260 add the diskfile device name if you don't furnish one.
Lines 1300-1340 add the "official" object-file extension if you don't feel like typing it.
Lines 1400-14S0 attempt to OPEN the file. If the OPEN fails, these display a simple error message and let you try agaln.
Lines 1500-1540 insist that the first two bytes of the file are the values that the Assembler always uses to start an object output. If so, they clear the screen and enable the display of control characters. The curved arrow symbol in line 1530 stands for the "clear screen" character. To get the correct character into your program, type ESCape and CTRL-CLEAR.
Lines 1600-1680 begin the main file-reading loop. At this point the first two bytes are the low- and high-order bytes of the starting memory address (FIRST) of the code segment. The next two are the ending address (LAST). Line 1660 computes the number of bytes in the code segment (COUNT).
Lines 1700-1960 print the code lines. Line 1710 computes the proper index into our output string. That string will contain all the byte values and will display as a strange-looking bunch of characters ("lots of funny stuff"). Lines 1740 and 1750 limit the displayed lines of code to 80 string characters. The WEIRD flag is used to mark a "quote" or "end-of-line" value. The loop from 1770 to 1860 reads the bytes and displays them. If it finds a weird one, it puts a "period" on the screen and remembers the value and position in BLOCK$. Line 1870 finishes the string. Lines 1880 through 1930 check for and handle the weird characters by displaying lines to insert them in the string as CHR$ values.
Lines 2000-20S0 do normal end processing. Line 2010 saves the current cursor position. Lines 2020 and 2030 put a DlMension statement for our string at the top of the screenb
Lines 2100-2210 print error messages. We get to them from TRAPs or GOTOs back in the main code.
Lines 2300 through 2330 change control-character display back to normal and clean up. Line 2330 clears the program out of memory.
THE SUBROUTINES
I've included two Assembly Language subroutines, MOVE and SPRAY. MOVE puts Assembly code where you want it. SPRAY is great for zeroing your Player/Missile memory before you use It.
THE TECHNIQUES
There are three Assembly code storage techniques that I use. Each has its advantages. For the explanations, assume that the Charger program left the following lines on your screen:
9999 DIM CODE$(62)
9999 CODE$(1)="lots of funny stuff"
To add the Assembly code to your BASIC program, you can either LOAD it and then edit the lines, or you can edit them, LIST them to a file, and ENTER them into your program. I'll just explain how you would edit the lines for whichever storage technique you want.
The first technique saves the most memory. It results in only one copy of the Assembly code in your BASIC space. The code must not have any absolute address references within itself. You can't use a JMP or LDA instruction with an absolute address within your Assembly Language module. Set a variable equal to the address of a string constant containing your code. You would not need the DIM line at all. You would edit the other to look Like:
9010 SPRAY=ADR("lots of funny stuff")
You have named your module SPRAY. SPRAY is the address you would use in, for example, a USR function as the address of the code to run. A slight variation is to use ADR on the string constant right in the USR function .
Be careful BASIC doesn't move things around and invalidate your address. I've never had a problem with that, but I always do all my DlMensions and initialize such addresses at the beginning of execution.
The first technique has no clean way to handle the unprintable characters. The second technique can, but results in a wasted copy of your Assembly code. In the second technique, you put the code into a string variable and execute it from there. This requires the same restriction on absolute addresses, but it lets you tuck the weird characters in where they belong. You would edit the lines something like this:
1010 DIM SPRAY$(62)
1015 SPRAY$(1)="lots of funny stuff"
You might also add a line to set SPRAY to the ADR of SPRAY$, or simply use ADR(SPRAY$) as the address when you need it. My example didn't need this technique; it had no weird characters. If it did there would have been a line like CODE$(5,5)= CHR$(155).
The third technique handles weird characters okay. It wastes a copy of the code, but has the advantage of allowing absolute addresses if you put the code into a fixed place, like Page Six. You use the MOVE routine for this one.
You can use a combination of the first two techniques to accomplish the third. MOVE the code to where you want it. MOVE can be in a constant or a variable. It might look like this:
9010 MOVE=ADR("MOVE's funny stuff")
9020 DIM CODE$(62)
9022 CODE$(1)="lots of funny stuff'
9024 TEMP=USR(MOVE,ADR(CODE$),1536,LEN(CODE$))
In this case, the code will go to Page Six (1536 decimal destination address). TEMP is any unneeded variable, since MOVE returns no value.
Bob Stewart is propietor of The Logic Smiths, Groton, Mass., a software development company specializing in Atari products. The main product so far is The Next Step, offered by OnLine Systems (see ANTIC, #5, p. 42). Bob has been programming and designing software for 13 years.
[code]