The Main Loop
Eval is the heart of LADS. It is the main loop. It starts assembly at START (line 30) and ends assembly at FINI (line 4250). Throughout Eval, JSRs take us away from the main loop to perform various other tasks, but like mailmen, all the other routines in the assembler start out from Eval, the post office, and they all RTS back to it when their work is done.
For convenience, references to lines within the source code listing at the end of the chapter are boldface inside parentheses. Also, to distinguish label names like FINI from the names of one of the 13 sections of LADS (a subprogram like Eval), we'll put label names in all caps, but just capitalize the first letter of the subprograms of the assembler.
Most programs have a brief initialization phase, a series of steps which have to be taken to fix things up before the real action of the program can commence. Variables have to be set to zero, files sometimes have to be opened on a disk, defaults have to be announced to the program. (Defaults are those things a program will do unless you specifically tell it not to. A game might default to single-player mode unless you do something which tells it that there are two of you playing. LADS defaults to hexadecimal numbers for printer or screen listings and turns off all its other options.)
At its START, LADS loads the Accumulator with zero and runs down through 48 bytes of registers, flags, and pointers, stuffing a zero into each one. These flags are all needed by LADS to keep track of such things as which pass it's on, whether or not you want a printer listing, or want the results of an assembly to POKE into memory, or whatever. This initialization fills them all with zero. The label OP is the highest of these registers in memory, so we LDY with 48 and DEY down through them (see line 30).
Let's take a minute to briefly review our terminology:
Register usually refers to the Accumulator (A), or the X or Y Register in the 6502 chip. It can also mean a single byte set aside to temporarily hold something. It's like a tiny buffer.
A buffer is a group of continuous bytes used to hold information temporarily. An input buffer, for example, holds the bytes you type in from the keyboard so they can be interpreted by BASIC. The bytes stay there until you type RETURN, BASIC stores the information into your program, and you type a new line into the input buffer.
A flag is a byte which is either on or off (contains either zero or some number) and signifies a "do it" or "don't do it," yes or no, condition. Of course, a single byte could hold a number of flags because each bit could be on or off. In fact, the Status Register in the 6502 chip does just that-it's only a single byte, but its bits are flags tested by CMP and the BNE, BEQ-type instructions. When you need a flag, though, it's easier to just use a whole byte and test it for zero or not-zero. An example of a flag in LADS is the PRINTFLAG. If nonzero, the assembler sends a printout of the assembly process to a printer. If zero, the printer remains silent and still. You set (turn on) the print flag with the pseudo-op P; otherwise, the default is no printing.
A pointer holds a two-byte address. Many times pointers are put into zero page so they can be used by Indirect Y addressing: LDA ($FB), Y gets the byte from the address held in $FB and $FC (seen as a single, two-byte-long number). If
(remember that the 6502 expects these numbers to be backward; this two-byte group means $1500) then LDA ($FB),Y will load the A register (the Accumulator) with whatever byte is currently in address $1500. We can set up our own pointers. If they're not in zero page, they're likely holding some important address which a program needs to remember. In LADS, ARRAYTOP is such a non-zero-page pointer; it tells LADS where to start looking through the label table for a match. We'll look into this when we get to the subprogram Arrays.
Cleaning the Variables
At its start LADS must initialize its variables. If we didn't fill them with zero, there could be some other number in these bytes when we fire up LADS and that could cause unpredictable results. Then (80) we get the low byte of the start of LADS (using the pseudo-op #<START) and put it in the low byte of MEMTOP (used by the Equate subprogram). We also put it into the pointer BASIC uses to show how much RAM memory it has available, BMEMTOP (line 70 in Defs). And, finally, put it in ARRAYTOP. ARRAYTOP will show where the LADS' data base of labels starts in memory (it builds downward from the location of LADS).
Then we take the high byte of START and put it into the high bytes of these three pointers.
Now for the defaults. There is only one. We want listings to be in hexadecimal unless we specifically direct the assembler otherwise with the NH, no hex, pseudo-op. So we put #1 into the HXFLAG. The rest of the flags are left at zero. If you want different defaults, put #1 into some of the other flags. For example, if you usually want to watch the results on screen during an assembly, just create a new line: 185 STA SFLAG. This will cause a screen disassembly every time you use LADS. Putting this default into LADS itself merely saves you the time of adding the S pseudo-op if you generally do want to watch the assembly onscreen. That does slow up the assembler, but with shorter programs, you might not notice the difference.
Where's the Source File?
LADS needs to know what you want to assemble. If you're using the RAM-based version of LADS (see Chapter 11), there's no need to give a filename to LADS; just SYS, and LADS will assemble what's already in RAM. But if you're in the normal LADS mode, assembling from a disk file, you'll have to announce which file. LADS looks at the upper lefthand corner of the screen to read the filename (190). If it finds a space #32, it checks for another space (310) before giving up. This way you can have continuous names like FILENAME as well as two-word names like FILE NAME. Whatever it finds onscreen, it stores in the buffer FILEN. It also takes care of characters which are below the alphabet in the ASCII code by adding 64 to them if they fall below 32 (240). The Atari version asks for the filename from the keyboard in the manner of a BASIC INPUT command.
When the filename is stored in the buffer, we JSR to Openl, the subprogram which handles all I/O, all communication with peripherals. In this case, communication will be with the disk drive.
After the file is opened for reading, we JSR to another subprogram, Getsa, the get-start-address routine. It just looks for *= (the start address pseudo-op) and, finding it, returns to Eval where the number following that symbol will be evaluated. If it doesn't find a *=, that can only mean two things. Either there is no program on the disk by the name you put onscreen or LADS did find the program, but no starting address was given as the first item in the source code. Both of these situations are capable of driving LADS insane, so Getsa aborts back to the safety of BASIC after leaving you a message onscreen.
This SMORE routine (370) will be used again when we've completed the first pass of the assembly process. The first pass goes through the entire source file, storing all the names of the labels and their numeric values into an array.
When we finish making this collection of labels, our label array, we've got to make a second pass, filling in the opcodes and replacing those labels with numbers. It's here, at SMORE, that we jump to start the second pass.
A zero is given to ENDFLAG to keep the assembler running. If the ENDFLAG is left up, is not zero, the assembler assumes it has finished its job and stops.
The initialization is completed with a JSR to the subprogram Indisk which pulls in the number you wrote as the starting address following *=. This number is left in LADS' main input buffer called LABEL. Before dealing with this number, though, we check to see if we're on the first pass (410) and, if so, print the word LADS onscreen after a JSR PRNTCR which prints a carriage return. Routines beginning with PRNT like PRNTSPACE and PRNTLINE are all grouped together in the subprogram Findmn. They're used by most of the subprograms and print various things to the printer or screen.
Now we need to put the starting address into the pointer SA which always holds the current target for any of our assembled code during execution. If the HEXFLAG is up, that means you wrote something like * = $5000 and hex numbers are translated by the subprogram Indisk before it RTSs back to Eval. Decimal numbers like *= 8000, however, are not translated into the two-byte integers that ML (machine language) works with, so we need to send decimal numbers to Valdec (another subprogram) to be turned into ML integers (610). The pointer called TEMP is made to point to LABEL so Valdec will know where to look for the number.
It's important to realize that numbers coming in from the disk or from RAM memory are in ASCII code, as characters, not true integer numbers. That is, the characters in a number like 5000 will come into the LABEL buffer as they appear in RAM or on a disk file. 5000 would be (in hexadecimal notation) 35 30 30 30; these are the character codes for 5-0-0-0. It's Valdec's job to transform this into 00 50, an ML integer. When we get to Valdec, we'll see just how this is done. It's a useful technique to learn since any numbers input from a keyboard will also be in this ASCII form and will need to be massaged a bit before they'll make sense to ML.
Remembering the Start Address
When, at STAR1, we finally have an ML integer in the little two-byte variable called RESULT, we can transfer the integer to SA. And we put the integer into the variable TA, too, so that we'll have a permanent record of the starting address. SA will be dynamic; it will be changing throughout assembly to keep track of the current assembly address. It will be LADS' Program Counter. TA will always remember the original starting address.
By this time you might be thinking that all this is hard to follow. TA and RESULT and LABEL don't mean much at this point. We've plunged into Eval, the most condensed, the most intensive, section of the entire program. As the main loop, Eval will send tasks to be accomplished to many subroutines, in subprograms which we've not yet examined. It's like landing in a strange city without a map. You see street signs, but they mean nothing to you yet. But this is one of the best ways to learn if you can be patient and ignore the temporary gaps in your knowledge and the momentary sensations of confusion.
We're gradually building a vocabulary and mapping out some of the pathways which make up the language LADS and the ways the ML works. The subprograms are, by and large, easier to follow. They're more self-contained. But bear with this tour through Eval. It makes what follows easier to grasp and offers a foundation-however unconscious at this pointfor a deeper appreciation of the ways that ML does its magic.
The Main Routine
Every line of source code which LADS examines begins with STARTLINE (690). The ML between STARTLINE and P (5520) is, in effect, an assembler. The rest of the routines and subprograms deal with the niceties, the auxiliary efforts of the assembler-pseudo-ops, built-in arithmetic routines, I/O, printout formatting, and so forth.
In fact, this section of LADS is based on the BASIC assembler, the Simple Assembler, from my previous book, Machine Language for Beginners. If you want to see how a large BASIC program can be translated into ML, you might want to compare the Simple Assembler to the rest of Eval. There are some comments within the listing of LADS' source code which refer to the BASIC lines within the Simple Assembler (see lines 3270 and 3410 for examples), and a number of the labels, starting at 4670, also refer to their BASIC line number equivalents in the Simple Assembler. L680 is a label to LADS, but is also a reference to an equivalent line, 680, in the BASIC of the Simple Assembler.
It's LADS' job to take each line in the source code and translate it into runnable ML object code. LADS would take the source line 10 LDA #15 and change the LDA into 169 and leave the 15 as 15. The value 169 is the ML opcode for the Immediate addressing mode of LoaDing the Accumulator. Then LADS would send these two bytes of object code, 169 15, to any of four places depending on what destinations you had specified as pseudo-ops in the source code. The D pseudo-op would send 169 15 to a disk file, P to the printer, .S to the screen, and .O directly into RAM memory.
When LADS first looks at at each source code line, STARTLINE checks the ENDFLAG to be sure it's safe to continue. If ENDFLAG is zero, we BEQ to the JSR to Indisk. (Otherwise, the program would go down to FINI and close up shop, its work finished.)
Indisk is the second largest subprogram, and LADS will be gone from Eval a long time by the computer's sense of time. For us, this detour happens in a flash, and a lot happens. Indisk can even JSR into other subprograms, but we'll see that in a later chapter. All we need to realize now is that each source line needs to be pulled onto our examination desk so LADS can pick it apart and know what to assemble.
Our examination desk is the buffer called LABEL. First a line of source code is laid out on the desk. To prepare for the exam, we put down the EXPRESSF(lag) and the BUFLAG, although they might be raised again during the evaluation to come. EXPRESSF tells LADS whether the expression following a mnemonic like LDA is a label or a number. It signals the difference between LDA SPRITE and LDA 15. BUFLAG tells whether or not there is a REM-like comment attached to the line under examination. If there is a comment, we'll want the assembler to ignore the remarks, but the screen or printer should nevertheless display them.
Now, as we often will, we check PASS (760) to see if it's the first or second time through the source code. On the first pass, we're not going to print things to a printer or the screen, so we'd jump to MOE4 and ignore the next series of printouts.
But if it's the second pass, we check the SFLAG, the screen flag, to find out if we should print to the screen. If the answer is yes, we print a line number, a space, the SA (current address), and another space. Don't worry about LOCFLAG just yet.
Now we want to know if there's any math to do. PLUSFLAG is up when the line contains something like this: LDA SCREEN+5. If it does, we briefly detour to the subprogram Math to replace SCREEN+5 with the correct, calculated number.
The Inner Core
Now we're at the true center, the hot core, of LADS: Line 900 is the pivot around which the entire structure revolves. This JMP to Findmn accomplishes several important things and sets up the correct pathways for the assembler to follow in the future. Findmn finds a mnemonic. Say LADS is examining this line:
10 LDA 15
After Findmn does its job and JMPs back to Eval, there would be a 1 in the TP register (it's like a BASIC variable, called TP for "type"). And there would be a 161 in the OP, for opcode, register.
That 161 is not the number we'll want POKEd into memory. 161 is the right number for the LDA (something,X) addressing mode, but it's wrong for the other modes, including LDA 15. Nevertheless, any LDA will first get a 161, the base opcode. It's the lowest possible opcode for an LDA; the other LDA addressing modes can be calculated by adding to 161. LDA 15 is Zero Page addressing and its opcode is 165. Eval's main job is to start off with the lowest, the base opcode for a particular mnemonic like LDA, and then make adjustments to it when the correct addressing mode is detected. Eval establishes the addressing mode when it examines the line and looks for things like the # symbol and so forth. As we'll see, this examination will modify the OP number until the correct opcode is calculated.
For now, though, it's enough that we return from Findmn with a base opcode number, something reliable to work from, stored in the variable OP. By the way, Findmn gets these numbers, TP and OP, from a table in the subprogram Tables. We'll look at it at the very end of our exploration of LADS in Chapter 9. Tables is where all the constants are stored.
When No Match Is Found
Sometimes Findmn won't find a match when it looks through the table of mnemonics in the subprogram Tables. This means that the first word in the line under examination was not a mnemonic. If this happens, Findmn returns (via a JMP) back into Eval where labels are analyzed. Eval then knows that this first word isn't one of the 6502 commands. Instead, it must be a label.
Labels in this first position in a line can be of two types: address labels and equate labels. An address label identifies a location within the program that will be the target for branches, jumps, JSR, etc. It's like giving names to subroutines so you could later JSR PRINTROUTINE. Here's an example:
100 START LDA #0
After the assembler finishes assembling this, we'll have:
100 3A00 A9 00 START LDA #0
The OP 161 has been changed to 169 (the hex number A9 in the example above), and we'll see how that was arrived at presently. But START has had no visible effect. It's just listed there, but doesn't affect the A9 or 00. START is a place marker. It hasn't been ignored. During the first pass, LADS stored START in an array along with the 3A00 address. That's why START can be called an address label. This is very much the way that BASIC reads a variable name, sticks it in an array, and puts the value of the variable up there with the name.
On pass 2, when all these labels are needed, the correct address will be there, waiting in the array. If LADS comes across a JSR START or a BEQ START, it will be able to search the array and replace the word START with the right number, the address.
The other possible kind of label is the equate label. It looks like this:
1100 SCREEN = $0400
It, too, is stored during the first pass and looked up during the second pass. But the equals sign shows that we should remember the value on the other side of the = symbol, not the address of the location of the label. In this example, whenever we want to store something onscreen, we don't need to calculate the correct address. $0400 is the first byte in screen memory (on the Commodore 64 in this example). So we can just STA SCREEN to put whatever is in A into the upper lefthand corner of the screen. Or STA SCREEN+200, or STA SCREEN+400, or whatever. (Adding numbers to SCREEN will, in this case, position our A lower on the screen.)
It's here that we decide whether we're dealing with one of the labels or with an ordinary mnemonic. If we JMP back from Findmn to EVAR (920), the first thing on the source code line was a mnemonic. If we JMP back from Findmn to EQLABEL, it wasn't a mnemonic (hence it's a label). EVAR evaluates the argument, the 15 in LDA 15. EQLABEL evaluates the other kind of argument, the label SPRITE in LDA SPRITE.
Simple and Other Types
Some of the mnemonics are quite straightforward. They've got no argument at all: INY, ROL, CLC, DEC, BRK, RTS, etc. There's no argument to figure out, and all of these selfcontained instructions have the same addressing mode, Implied addressing. Fully 25 of the 56 mnemonics are of this type. We've called them type 0 (see the chapter on the Tables subprogram for an explanation of the types), and so Findmn puts a 0 into the TP variable. Our first step in the evaluation of any argument (920) is to check the TP, and if it's 0, go to the type 1 (meaning only one byte, the opcode itself) area. There, the single byte will be POKEd and printed if you've requested that with your pseudo-ops. And then we can go on to fetch a new line.
If it's a more complicated addressing mode, though, we continue evaluating, comparing it to type 3 (940). If you want, you can look up the mnemonics and the parallel types and ops tables in the Tables subprogram. Type 3's are the bit-moving instructions ROL, LSR, ROR, and LSR. They have a pattern of possible addressing modes in common. (It's this common pattern of addressing modes which underlies these types. They share the same potential addressing modes and can be evaluated and adjusted as a category rather than individually.)
In any case, we turn them into type 1 and then look at the fourth position in the storage buffer LABEL. If we could peer into this buffer, we might see either:
That bare ASL is not an implied address like INY and CLC and the rest of those self-contained instructions we discussed above. These bit-moving instructions (ASL, ROR, etc.) are just like type 1 (LDA, etc.) with this single exception: They can have a special addressing mode all their own called Accumulator addressing. It's a rare one. In this mode, ASL would Arithmetic-Shift-Left the number in A, the Accumulator.
The point to grasp here is that, rare as a nude ASL is, we've got to include it in the assembler. So we check to see if there is a zero in the fourth position in our buffer, LDA LABEL+3. A zero means end-of-line. So we can detect from a zero that there is no argument and, hence, this is a case of Accumulator addressing. If it is, we need to add 8 to the base opcode for these bit-movers and then jump to the type 1 exit. If it isn't, we've already turned it into a type 1 (970) and from here on, we'll treat it as a member of that family. In effect, type 1's can have several addressing modes, so we must evaluate the mode. We go to EVGO.
Fat Y Loops
Before entering most ML loops, you'll first LDY #0. Y often functions as a counter, so it's set to zero, and then INY occurs at the end of the loop. But some loops require that we INY at the start or at least early within the loop. In such cases, we must LDY #255 before entering the loop. The first event within the loop is an INY, so in effect, Y becomes 0 right off the bat. When you increment 255, you get a zero.
EQLABEL is where we determine what kind of label we're dealing with. On the first pass, we don't care. All labels must be stored in our label table array for later reference on pass 2. On pass 2, though, we must go through the test in EVX1 (1090). And it's one of those fat Y loops that start off with a bloated Y Register. We put 255 into Y at the start.
We load the first character in the LABEL buffer. If it's zero (end of the line), there wasn't any argument. There should have been. This is a mistake. By this time, there has to be an argument. We've already eliminated the only addressing types that have no argument: Implied (type 0) and Accumulator (a variant of type 3). If there's no argument, the source code is defective. There should be an argument. We've got to print an error message.
NOAR is tucked away at line 520 of the Equate subprogram. We'll get to it later. It just prints a "no argument" error message. But we should clear up the little mystery surrounding the bounce we just took. We BEQ GONOAR (1110) only to JSR NOAR (1320). Why? This is one of those springboards we discussed in Chapter 1.
The B instructions, the branchers like BEQ, can move us only 127 bytes in either direction, forward or backward, from their location. This is sometimes not far enough. LADS will alert you to this if you should try to branch further than you can. It will print BRANCH OUT OF RANGE and ring the bell. The easiest solution to this problem is to simply have the branch go to a nearby JMP or JSR. They can fly off to any address in the computer. Have them act as springboards, bouncing you to your actual target.
The alternative is to move your target closer to the branch. The target is probably a subroutine. But moving a subroutine is often a lot more trouble than simply creating a springboard.
Back to the evaluation (1120). If there is an argument, we move it up to another buffer called FILEN. Then we check for the blank character, 32, before leaving this loop. The label name gets moved up to FILEN for further analysis. Then we INY and look at the next character.
Which Kind of Label?
If the first thing after a blank character is =, we've got an equate label like:
100 NAME = $500
If it is an equate label, we ignore it because we're on the second pass here. Line 330 sends us over this section if it's the first pass. There's no need to pay any attention to equate labels on the second pass, so we jump to INLINE, the preparations for getting a new line to evaluate.
But it might be the other type of label, an address label like:
100 START LDA #15
On pass 2 we can also ignore START, the label part of this line. Both types of labels have already been safely stored in our array during pass 1. Nevertheless, following the addresstype label is some code we cannot ignore. On pass 2 LADS must assemble that LDA #15.
NOTEQ (not equate type) moves the address label up to a buffer called FILEN while at the same time moving the LDA #15 over to the start of the LABEL buffer. It's doing two things at once. This is how these buffers look before NOTEQ
LABEL START LDA #1500000000000
and after NOTEQ:
LABEL LDA #150A #1500000000000
START is up at FILEN and can be printed out later for a listing. But what good is that mess in the LABEL buffer? It will work perfectly well because that 0 in the eighth position is the delimiter. It tells LADS to ignore any random characters following it. Remember that these numbers are stored in memory as ASCII code, not as literal numbers. 15 would be stored as 49 53. 150 (the number 150) would be stored as 49 53 48. But a different kind of 150, where that final 0 is a true zero, a delimiter, would be stored as 49 53 0. So when we go to look at and assemble the information in LABEL, LADS will only work with LDA #15 and ignore the OA #150000, etc., the remnants of the old line. All is now ready for the assembler to take a look at a mnemonic and its argument, so we JMP to MOE4 (1310). If this had been pass 1, we would have bypassed all this and leapt from 1070 right down to 1330, where we go to the subprogram Equate, which stores labels and their values in the label table array. But both pass 1 and pass 2 must continue to work out the addressing modes by going to MOE4. Why should we need to worry about addressing modes on pass 1 since LADS doesn't POKE anything into memory or save anything to disk during pass 1?
LADS must keep an accurate PC (Program Counter) during pass 1 to know what value to assign to address type labels. Otherwise, the address labels would be inaccurate:
10 START INC 15
20 LDA 15
30 BEQ FINISH
40 JMP START
50 FINISH RTS
Notice that both INC 15 and LDA 15 are Zero Page addressing. They occupy two bytes in memory. But they could have been Absolute (LDA 1500) addressing, or other modes which use up three bytes. LADS has no way of knowing, by reading LDA or INC alone, whether to raise the program counter by two or by three. All this wouldn't matter much except for that label FINISH in line 50. It has to be assigned its proper address during pass 1 and stored in the array. That means LADS needs to know exactly how many bytes it is from START to FINISH.
Consequently, LADS has to check out the arguments of INC and LDA to see whether they're addressing modes using up two or three bytes. This Program Counter is kept in a variable in LADS called SA. It's constantly changing during both passes of the assembly, but it is used during pass 1 to assign numbers to address labels like START and FINISH.
We'll deal with the next routine, EVEXLAB (1360), shortly. Let's go first to MOE4 and see how LADS analyzes arguments.
We've Been Here Before
Recognize MOE4 (900)? We already discussed it. It JSRs to FINDMN and JMPs back to EVAR (920) having recognized a 6502 mnemonic or JMPs to evaluate a label if it didn't recognize a mnemonic. In our example, it will find LDA #15 this time, JMP to EVAR, and end up going to EVGO (from 950).
Here at EVGO, LADS has to decide whether it's dealing with a normal numeric argument like #15 or an expression label, a word like SOUND. Imagine that we'd started off by defining the label SOUND:
10 SOUND = 15
When we later wanted to indicate 15, we could substitute the word (LDA #SOUND) for the number (LDA #15).
EVGO distinguishes labels from numbers by using the ASCII code. In this code, letters of the alphabet have a numeric value 65 (the letter A) and go up from there. Thus, if the character in the fourth position (see line 1490) is less than 65, if it triggers a BCC, we don't raise the EXPRESSF(lag). That flag indicates a nonnumeric expression. In other words, the expression has a letter of the alphabet so it must be a label. Similarly, EVMO2A raises the Y offset and tests the fifth character. If it's a zero, we've got a single-letter label, like P (1540). Meanwhile, we're moving the label up to a buffer called BUFFER. And, again, we check for a character with a value lower than 65.
EVMO2 (1600) continues to move the label from one buffer (LABEL) to another (BUFFER). It only stops when it finds a zero indicating the end of the line. Note that both number expressions (arguments) like #15 as well as label expressions like #STOOL are moved from the LABEL buffer up to the BUFFER buffer. The only distinction between them is signaled by the raising of the EXPRESSF(lag) when there's a label rather than a number. For numbers, EXPRESS stays down, stays 0.
Hex Numbers Are Already Evaluated
EVM03 (1660) puts the label's size, the number of characters in the label, into the variable ARGSIZE and checks to see if the HEXFLAG is up. The HEXFLAG is sent up in the subprogram Indisk if a $ symbol is noticed as a line is streaming into LADS. So if HEXFLAG is BNE, not equal to zero, it's up and we can jump right down to L340, which starts to figure out the addressing mode. If the EXPRESF is up, that means a word label, not a number, so we have to go to EVEXLAB to get the number to substitute for the label. Otherwise, we've got a decimal number to work with as our argument (1730).
The whole function of lines 1730-1840 is to have the variable TEMP pointing to the first ASCII number in the label. That's why we keep INCrementing TEMP until we point to a character that is not BCC, less than the 0 ASCII character (48) in line 1830. Then we have to test for the ( left parenthesis or comma character. If it is one of them, it can put in a true zero as a delimiter.
When the number is properly set up, it is analyzed by the Valdec subprogram, which turns this ASCII string of numbers into an ordinary ML two-byte integer.
If, however, we were sent to EVEXFLAG (from 1710), it checks for something less than an alphabetic character (such as a ( or a # symbol). When it locates the first alphabetic character, it stores it into the variable WORK and JSRs off to the subprogram Array where the stored labels will be looked through. Then it joins up again with the numeric expressions by going to L340 for addressing mode evaluation.
How Is It Addressed?
This is the final job the assembler must perform-distinguishing between Immediate (LDA #15), Absolute (LDA 1500), Zero Page (LDA 15), Indirect Y (LDA (15),Y), and the other addressing modes. Recall that we've already eliminated nearly half the possibilities by previously handling type 0, the selfcontained, implied ones like CLC and INY. What's left is to check for # and ( symbols and to see how big the argument is. That tells us if our argument (the expression) calls for Zero Page addressing or not.
First off, LADS checks for the # character (2130) and, finding one, goes to the IMMED routine to handle Immediate addressing. Next it looks for the ( character. Finding one of those, it goes off to the INDIR routine to deal with Indirect addressing.
Failing to find either of these symbols, it loads in the type variable, TP, and looks to see if it's an 8. All the B instructions, the branches like BNE and BCC, are grouped together as type 8. Finding a type 8, LADS goes to the REL subroutine to handle Relative addressing.
From here (line 2220) to the end of Eval, there will, from time to time, be adjustments made to the OP variable which are neither easy to explain nor easy to immediately understand. They're based on the logic of the interrelationships between the various addressing modes. For example, if we've reached this point (2220) without branching to one of the routines like IMMED, INDIR, or REL, we now need to add 8 to the opcode value. Why? It just works that way. If you're truly interested, study the table of opcodes and you'll begin to notice certain similarities between the opcode for LDA absolute and INC absolute, etc. It's not necessary to work all this out. For a detailed discussion of the logic of these adjustments to OP, see the explanation of the Tables subprogram in Chapter 9.
At any rate, INDIR looks at the character of the argument in BUFFER and sees if it's a ) symbol. If not, and it's type 1, we add 16 to OP. If we have a type 6, we know we've got an indirect JMP, so we go there. Otherwise, we go to TWOS, where two-byte addressing modes, like LDA (15),Y, are handled.
JIMMED (2420) is one of those springboards to handle a BRANCH TOO FAR for an unassisted B instruction with its 127-byte reach.
The Hardest Part of LADS
REL handles the B group. This was the hardest part of LADS for me to write. For some reason, I kept hoping for a simple way to test and translate forward and backward branches. No simple way presented itself. There may be a more clever solution than the one you'll see described below, but I couldn't find it and had to go on.
REL first checks PASS. On pass 1, we simply go directly to TWOS. On pass 2, though, we look at RESULT. RESULT is a two-byte variable which holds the integer form of all arguments-labels, hex, or straight decimal. They're all left in RESULT by the various subprograms, Array, Indisk, and Valdec, which translate labels, hex ASCII, and decimal ASCII. These three possible original forms of the arguments are translated into two-byte integers that can be POKEd into memory or saved on disk as parts of an ML program.
If we're on pass 2, we look at RESULT and now calculate the correct argument for a branch instruction. It requires that LADS first determine whether we're branching backward or forward in memory. It does this by subtracting SA (the Program Counter, the current address, the address of the B instruction to which its argument will be relative). It subtracts SA from RESULT, the argument of the B instruction:
100 1000 A0 00 START LDY #0
110 1002 C8 LOOP INY
120 1003 F0 03 BEQ END
130 1005 4C 02 10 JMP LOOP
140 1008 60 END RTS
The target, END, of the BEQ above is address 1008. The location of the PC at the BEQ is 1003. MREL (2470) first subtracts the PC in variable SA from the target's address. Remember that RESULT holds the correct integer after the Array subprogram looked through LADS' array and found the label END. So 1008 minus 1003 gives 5.
BPL and BMI
BCS tests the result of the subtraction-the carry is still set if the target is higher than SA and, consequently, we've got a branch forward. We BCS FOR. Otherwise, it's an attempt to branch backward in memory, and we test the high-byte result of the subtraction (the number in the accumulator) against $FF. That high byte must equal $FF, or we've branched too far and we go to the error-message printout routine (2570). Then we check the low-byte result of the subtraction (which was pushed on the stack temporarily in line 2500) to see if it's a correct value. The PLA (2580) will set the N flag in the Status Register if the number is greater than 127. We want it to be, since this is a backward branch. If this flag is not set, we BPL to the error message. Otherwise, we jump to the concluding routine, setting up a correct branch.
The FOR routine handles forward branches in a similar way, going to the error routine if the high byte is not zero (2610) or if the low byte has the seventh bit set (proving it's greater than 127, an incorrect forward branch).
Let's pause for a minute to see what BPL and BMI do for us in this test. In binary, $80 looks like this: 10000000. We don't care about the bits in the positions where the zeros are. We're only interested in the leftmost bit, the so-called seventh bit. Note, too, that PLA affects the N and Z flags in the Status Register.
After a PLA of 10000000, BPL would not branch anywhere, but BMI would. It would mean that the seventh bit is set, the "minus sign" in signed arithmetic was found. The sign in signed arithmetic is held in the seventh bit. 1XXXXXXX would signify a negative number, 0XXXXXXX a positive number. (There's a connection here with the fact that forward branch arguments can range from $00 to $7F, and backward branches from $FF down to $80.)
Now some people will point out that there are eight bits in a byte, and we keep referring to the seventh bit when we're talking about the eighth. Recall that, in computing, much counting begins with the zeroth bit. A byte can hold only the numbers 0-255. The lowest number it can hold is a zero. But that still means that there are 256 possibilities, 256 possible states for a byte: 1-255 plus 0.
Signed Arithmetic Branching
If all this seems an unnecessary detour into messy detail, consider how Relative addressing uses signed arithmetic to calculate where it should branch. When the 6502 chip comes upon one of the B branch instructions like BNE, it looks at the argument in a unique way. If the number is higher than 127, it knows it must go backward. If lower or equal, it must go forward. That's why you cannot branch further than 128 backward or 127 forward. The argument can't use the entire byte to hold a number-the seventh bit must be reserved to hold the plus or minus sign. Remember, if the seventh bit is set, it means minus. If clear, it means plus. BPL (Branch if Plus) is triggered when the seventh bit is clear. BMI responds to a set (1) seventh bit.
Take a look at the assembly in the example above. Line 120 shows that BEQ END became the opcode F0 and the argument is 03. 03 will take us to END because all branches are calculated from the address of the mnemonic following the branch instruction. Count three from address 1005. You hit END.
A branch backward, too, counts backward from the address of the mnemonic following the B instruction. All branches count from their own PC location plus 2. Look at a branch backward:
40 1000 A0 00 START LDY #0
50 1002 C8 LOOP INY
60 1003 D0 FD BNE LOOP
70 1005 60 END RTS
Here line 60 is branch backward, but the argument, $FD, is pretty strange. $FD looks like this in binary: 11111101. So the seventh bit is set signifying minus, a backward branch. $FD is 253 decimal. $FF would be -1, $FE would be -2, and $FD is -3. From address 1005, -3 lands us at 1002, LOOP, where we want to land. Luckily, we needn't perform these calculations. LADS will handle all branch arguments. But you might want to use BPL/BMI branches as well as signed arithmetic in your ML programming. It's sometimes worth knowing the details of how these things are handled by the microprocessor.
One final adjustment needs to be made before LADS can POKE in the correct argument for branches. This adjustment takes place at RELM, where both forward and backward branches end up, unless they were found to be out of range.
After the low byte of SA was subtracted from the low byte of RESULT (2500), we pushed it onto the stack with PHA. That's sometimes a convenient place to stuff something you want to set aside for a minute while you perform other calculations. You could STA A or STA TEMP or put it in other temporary holding variables, but PHA is safe as long as you remember to PLA to leave the stack clean. You don't want to keep PHAing, or your program will soon fill up the stack, resulting in an OVERFLOW error and a machine-wide collapse. The 6502 chip won't ignite, the CRT screen won't melt, but the program will grind to a halt.
When we have a BRANCH OUT OF RANGE error we are going to go down to the DOBERR routine at line 5800, but we do need to PLA in lines 2560 and 2620 to keep the stack clean.
If there is no error, we've saved the result of the subtraction of the low bytes (it sits in the low byte of the RESULT variable). That's the number we really care about anyway. A single byte is all that can be used as a branch argument.
To make it a correct branch argument, we've got to subtract 2 from it. This, you recall, is because all branches are calculated from the address of the mnemonic which comes just after the branch instruction. Counting starts from the B instruction's address, plus two. Subtracting two will fix this up for branches in either direction.
We've seen how LADS calculates the branch addresses. At this point in the source code, we come upon a continuation of evaluations of other addressing modes. EVM05 (2740) gets the size of the argument in order to enable us to look at the character second from the end: LDA (ZERO),Y has a comma in this second-from-the-end position. INX NAME does not. By now, the variety of possible addressing modes has been somewhat narrowed.
If we did find a comma in that second-from-last position, that means the label ends in X or Y and we go to XYTYPE to deal with it. Otherwise, we check to see if it's a JMP (opcode 76). MEV eliminates two other possible modes, both Zero Page, sending LADS to the TWOS, two-byte, line-ending events.
We're headed for TWOS by now in any case, but we need to once again adjust the value of the opcode in OP if the type in TP isn't 6 or 4.
TWOS, like TP1 (for one-byte-long instructions) and THREES, is where LADS goes after an addressing mode has been determined. The opcode has been correctly adjusted and waits in OP. The argument waits in RESULT. TP1, TWOS, and THREES are quite similar. TP1 doesn't have an argument, so it just JSRs to a subroutine within the subprogram Printops. There, the bytes are POKEd into memory or to disk and PRINTed to screen or printer. Then LADS JMPs to INLINE to prepare for the next line of code.
TWOS (2970) and THREES (3400) also JSR to that same subroutine in Printops (which POKES, SAVES, or PRINTs an opcode), and then TWOS and THREES JSR to PRINT2 or PRINT3 as appropriate to store or print the byte or bytes of the argument.
Immediate addressing (LDA #12) is a variation of TWOS, but it first must make one of those adjustments to the value of the opcode before JUMPing to TWOS (see line 950).
THREES also requires some opcode adjustments before storing or printing its bytes; PREPTHREES (3240-3390) accomplishes that.
The JUMP subroutine (3010) handles the mnemonic JMP. It's a special case because it can have a strange addressing mode called Indirect Jump. JUMP tests for this and makes the necessary adjustment to the opcode if it finds the ASCII code for a parenthesis, indicating an Indirect Jump, for example JMP ($5000).
IMMED handles the # type, Immediate addressing. It first looks to see if the #" pseudo-op is in effect (3100) and, if so, stores the argument directly from the buffer. Then IMMED adjusts the base opcode (in the OP variable) if necessary, and behaves like any other two-byte addressing mode, jumping to TWOS.
Preparations for a New Line
We come now to the cleanup routine, INLINE (3440). Its primary job is to handle the correct formatting of the printout of the source code. By the time LADS gets to INLINE, it's already printed a line's number, the address of the PC (the location of the code), and the object code bytes themselves:
line # /addr /bytes of object code
40 1000 A0 00
However, there are still three items to print: an address label (if any), the source code, and remarks (if any). To make listings easy to read, address labels should be set off by themselves, and source code should line up vertically on a printed page or screen:
line # /addr /bytes / addr label /source / comments
40 1000 A0 00 START LDY #0 ; begin here (entry)
Since each column should line up correctly, we're going to need to construct the ML equivalent of BASIC's TAB function. Those first three items-line number, address, and object code bytes-can take care of themselves. But any address labels must always be in the same position on a line. And since there can be one, two, or three object code bytes, the address labels wouldn't line up if we just printed a couple of spaces after the final object byte.
The first thing INLINE does is to check if we're on the first pass. Nothing gets printed out on pass 1, so we jump over the entire INLINE routine. If it's pass 2, we look to see if the screen flag, SFLAG, is up (3470). If it isn't, we again jump past INLINE.
Then the LOCFLAG is checked. It is up when there is a PC address label (like the label START in the example above). If it's up, we use something from BASIC: the cursor position byte. We've been using BASIC's PRINT routine all along. One of the advantages of this is that PRINT keeps a record in zero page of the current screen position; we could just LDA #20:STA CURPOS, and the next printout would be at position 20.
Tab to Printer
Things are more complicated, though, since LADS has an option to print listings to a printer as well as to the screen. We cannot use the same technique with a printer.
To find out how many blanks to print to the printer, it's necessary to subtract the CURPOS value from 20. Assume that we've printed 14 characters so far: 20 - 14 = 6. We use this result in a loop to print blanks to the printer (3660) to cause a simulated TAB.
Following the TAB, we're set to print an address label which is still waiting for us up in the buffer FILEN. As usual, we set TEMP to point to the message we want printed, and JSR PRNTMESS, thereby printing whatever is in FILEN, delimited by 0.
Source Code Printout
It's time to move over to the thirtieth position (on screen or printer) to the place where the source code is printed. This is handled basically the same way as the TAB 20 above. The main difference is the BEQ and BMI checks (3920) to take care of extra long labels. In most cases, your labels will be less than ten characters long, but LADS allows labels to be any length. How will we balance the need for neat, vertically aligned printouts against the option of labels of any length? How can labels which potentially range in length from 1 to 200 characters be formatted?
Since address labels always start in the twentieth position, and source code always begins in the thirtieth position, we've allowed ten spaces for address labels during printout. Onscreen, an address label 12 characters long would be truncated: STARTLINEHERE would be printed as STARTLINEH. But on the printer, the entire label would be printed and simply push the source code printout over. You can adjust any of these formatting options rather easily if they don't suit your needs. If you want to truncate address labels to five rather than ten character lengths on screen, just change LDA #30 to LDA #25 (3830).
In INLINE, we've done some output switching between screen and printer. We've called upon routines like CLRCHN, CHKOUT, and CHKIN. The protocol for using these routines is discussed in Chapter 5, the chapter on peripheral communications.
PRMMFIN (4000) prints the characters in the buffer LABEL. That will be the source code. Then, LADS checks to see if there was a < or > pseudo-op in this line. If so, it tags one of these symbols onto the end of the source code label. If your source code looks like this: LDA #>STARTLINE, the printout will be LDA #STARTLINE>. This will help to call attention to this special pseudo-op addressing mode. The < and > symbols are not buried within the label.
The underlying reason for doing things this way, however, is not .its visual appeal. It's easier and faster for LADS to analyze #STARTLINE than to analyze #>STARTLINE. During the analysis phase, LADS pulls out the < or > and raises BYTLFAG to show that the pseudo-op was originally a part of the label. Then it can assemble the label the same way it would assemble any other label.
The final job to be performed by INLINE is to check BABFLAG to see if there is a REMark, a comment, to print out (4100). The Indisk subprogam sends any comments to the buffer called BABUF to keep them safely out of the way. BABUF is the same buffer that BASIC uses for input. If there is a comment, we print a semicolon (4130), point TEMP to BABUF (4160), and PRNTMESS.
Then a carriage return is printed and we check to see if this was the final line of the source code. If ENDFLAG is set, we go to the assembly shutdown routine, FINI. If not, we pop back up to where we first started this line, STARTLINE, and pull in the next line of source code.
FINI: Which Pass?
As a two-pass assembler, LADS, of course, goes through the entire source code twice. When we get to FINI, we need to check which pass we're on. If it's pass 1, we INC PASS (from its zero condition, thereby setting it). After this INC, the next time we reach the end of the source code and come to FINI, we'll be sent to FIN, the shutdown routine.
But assume we've just finished pass 1 at this point. What we must do is reset the PC, the Program Counter. Back at the beginning, we saved the starting address in TA. SA has been LADS' PC variable, counting up and always keeping track of the current address during each event. Now it's time to reset SA by putting TA in it. Then we close the source code file on disk and promptly open it up again. This has the effect of resetting the disk's PC to point to the first byte in the disk file. Now we're ready to read in the source code all over again. We're ready to start the second pass.
We jump back up, just below START, to SMORE and read in, once again, the first line of the entire source code.
If we've already completed pass 2, however, we don't want to restart source code examination-everything's already accomplished, POKEd and PRINTed and SAVEd to disk as the case may be. We want to gracefully exit the assembler. FIN (4390) does this. It closes down any read or write files on disk, closes down communication to a printer, and jumps to BASIC mode. Now would be the time to try the object code program, to make some adjustments to your source code if you want, and then SYS back into LADS for another assembly.
Each computer has a "side entrance," a warm start into its BASIC. This entrance doesn't wipe out what's in RAM memory, doesn't blank out the screen. It's here that the LADS goes to move gently back into BASIC mode. The address of TOBASIC for each computer is defined in the subprogam Defs.
Evaluating ,X and ,Y
Although FINI is the logical end of the evaluation process, it's not the physical end of the Eval subprogram. Just below FINI is XYTYPE where such addressing modes as LDA $5000,Y are analyzed.
They too require some opcode adjustments before going to TWOS or THREES for printing and POKEing. We JMP to XYTYPE after having found a comma in a source code line like:
and so the Y Register is pointing to the character just beyond the comma when we arrive at XYTYPE. All we need to do is load BUFFER,Y to check if the character following the comma is an X or a Y. If it's an X, we jump down to L720 which handles X type modes.
Otherwise, we're dealing with something involving a Y addressing mode. It might be this:
so we have to check for the right parenthesis. We DEY DEY to move over to just in front of the comma and see if there's a ) symbol. If not, we've got a Zero Page Y addressing mode like LDX 10,Y or STX 10,Y. LDX and STX are the only two mnemonics which can use Zero Page Y addressing. They're rare. It's quite likely you haven't ever used them; it's possible that you haven't ever heard of them. But LADS must check for them just in case. LADS goes to ZEROY if there was no ) symbol.
LADS is likely to find the ), however, because Indirect Y addressing is a mode which is both common and useful. Encountering this mode, LADS goes to INDIR to process the Indirect addressing mode.
ZEROY (4660) is a somewhat misleading name, for it also handles the popular mode, Absolute Y: LDA SCREEN,Y. This addressing mode is not Zero Page. To find out whether it's dealing with the Zero Page Y, LADS checks the high byte of RESULT, the argument. If the high byte contains nothing, it must be zero page, and we process the opcode as such. If the high byte does contain something, the argument is thus larger than 255 and the opcode cannot use a Zero Page addressing mode. Again, the opcode is adjusted depending on the type (TP).
The routine at L700 (4950) prints out an error message because LADS was unable to calculate a correct addressing mode and the source code must contain a syntax error.
The concluding adjustments to the opcode take place between L720 and L809 (5040-5450). You might notice several JSRs to P in this section. P (5520) is a short subroutine which was used in debugging LADS, but was left in because you might want to use it when fixing up your own programs.
How P Works
P prints the current PC on screen, but doesn't destroy what's in the A, Y, or X Registers. Saving A, Y, and X is straightforward enough (5520), but where is the PC?
Whenever you JSR, the return address is pushed onto the stack. We can pull it off the stack with PLA, transferring its two bytes (one to the X Register and one to the Accumulator), and then push it back on with PHA. That leaves the stack ready to RTS correctly, but a copy of this RTS address is now in the registers as well, OUTNUM is a BASIC routine which normally prints line numbers during BASIC's LIST. But it will print any integer number if the low byte is in X and the high byte is in A. (See Atari notes for Atari's OUTNUM.)
Character $BA on Commodore machines is a check graphics symbol , and it's a convenient way to show that what follows is not part of a normal LADS printout. You could use any other symbol to highlight the special nature of the number being printed by P. What's important is that you are alerted to the fact that somewhere within your ML program, you did JSR to P. And the number that P prints will be the address of that JSR.
How is P useful? An ML program is like a rocket. It's so fast that you need to send up balloons now and then just to mark its passage from subroutine to subroutine. When you're not getting what you expect (and that's often in large, interacting ML programs), you can put JSR P into various parts of the program. Then, as the program zips along, you'll be able to see what's happening and in what order it's happening.
P is like setting BRK into the code or putting STOP into a BASIC program. The difference is that P just gives you a simple location report and lets the program continue, uninterrupted. If you wanted more information, you could expand P to print the registers at the same time. With that, you'd be on your way toward constructing the single-step debugging feature available in some monitor programs.
CLEANLAB (5720) is janitorial. It wipes the main buffers clean. It puts 80 zeros into LADS' main input buffer starting at LABEL (see Chapter 9, where the Tables are described). We don't want any remnants of the previous line left over to confuse things.
Finally, DOBERR is the error message printout routine for branches out of range. It rings the bell (ERRING), prints the offending line number, then points TEMPS to its message (stored with the other messages in the Tables subprogram), and jumps to TWOS so that the Program Counter will still be correctly increased by two.
Now we've seen the innards of Eval, the main evaluation engine, the heart of the LADS assembler. It's time to turn our attention to the data base managers Equate and Array. They build and search the array of labels.
Program 3-1. Eval
10 ; "EVAL" MAIN EVALUATION ROUTINE (SIMPLE ASSEMBLER)
30 START LDA #0
40 LDY #48
50 STRTLP STA OP,Y; -- LOOP TO CLEAR FLAGS --
70 BNE STRTLP; --------
80 LDA #<START; STORE BOTTOM OF LADS INTO TOP OF ARRAY/MEMORY. PROTECT IT.
90 STA MEMTOP
100 STA BMEMTOP
110 STA ARRAYTOP
120 LDA #>START
130 STA MEMTOP+1
140 STA BMEMTOP+1
150 STA ARRAYTOP+1;--------
160 LDA #1; -- SET DEFAULTS --
170 ; HERE YOU CAN SET ANY ADDITIONAL DEFAULTS YOU WISH
180 STA HXFLAG; TURN ON HEX LISTING FLAG
190 STMO LDA SCREEN,Y; -- GET SOURCE FILE NAME --
200 CMP #32
210 BEQ STM1; CHECK FOR ANOTHER BLANK
220 BCS STM3
240 ADC #64; ADJUST FOR LOW ASCII CHARACTERS
250 STM3 STA FILEN,Y; STORE CHARACTER IN FILENAME BUFFER
270 JMP STMO; GET ANOTHER CHARACTER
280 STM1 STA FILEN,Y; CHECK FOR 2ND BLANK
300 LDA SCREEN,Y
310 CMP #32; IF NO 2ND BLANK SPACE
320 BNE STMO; THEN GO BACK FOR MORE NAME (MIGHT BE 2 WORDS)
340 STY FNAMELEN; STORE FILENAME LENGTH
350 JSR OPEN1; OPEN READ FILE (SOURCE CODE FILE ON DISK)
360 ;------------------ RE-ENTRY POINT FOR PASS 2 ------
370 STORE JSR GETSA; POINT DISKFILE TO 1ST CHARACTER IN SOURCE CODE
380 LDA #0
390 STA ENDFLAG; SET LADS-IS-OVER FLAG TO DOWN
400 JSR INDISK; GET A SINGLE LINE OF SOURCE CODE
410 LDA PASS; IF 2ND PASS
420 BNE STARTLINE; THEN JUMP OVER PRINTING OF LADS NAME
430 JSR PRNTCR; PRINT CARRIAGE RETURN
440 LDA #230; PRINT BLOCK GRAPHICS SYMBOL
450 JSR PRINT
460 LDA #76; L
470 JSR PRINT
480 LDA #65; A
490 JSR PRINT
500 LDA #68; D
510 JSR PRINT
520 LDA #83; S
530 JSR PRINT
540 JSR PRNTCR; ANOTHER CARRIAGE RETURN
550 CKHEX LDA HEXFLAG; IF START ADDRESS NUMBER IS HEX, IT'S ALREADY TRANSLATED
560 BNE STAR1
570 LDA #<LABEL; IN THE LABEL BUFFER IS SOMETHING LIKE: *= 864
580 STA TEMP; PUT THE ADDRESS OF THE BUFFER INTO THE POINTER CALLED TEMP
590 LDA #>LABEL
600 STA TEMP+1
610 JSR VALDEC; TURN ASCII NUMBER INTO A TWO-BYTE INTEGER IN "RESULT"
620 STAR1 LDA RESULT; -- STORE OBJECT CODE'S STARTING ADDRESS IN SA,TA --
630 STA SA
640 STA TA
650 LDA RESULT+1
660 STA SA+1
670 STA TA+1
680 ;---------------- ENTRY POINT FOR EACH NEW LINE OF SOURCE CODE ----
690 STARRTINE JSR STOPKEY:LDA ENDFLAG:BEQ EVIND:JMP FINI; END LADS ASSEMBLY IF
700 ; EITHER THE STOP (BREAK) KEY IS PRESSED OR IF THE ENDFLAG IS UP.
720 EVIND JSR INDISK; OTHERWISE GO TO PULL IN A LINE FROM SOURCE CODE
730 LDA #0
740 STA EXPRESSF; SET DOWN THE FLAG THAT SIGNALS A LABEL ARGUMENT LIKE LDA P
750 STA BUFLAG; SET DOWN THE FLAG THAT SIGNALS # OR ( DURING ARRAY CHECK.
760 LDY PASS;ON PASS 1, WE DON'T PRINT LINE NUMBERS, ADDR. OR ANYTHING ELSE
770 BNE MOREEV
780 JMP MOE4
790 MOREEV STY LOCFLAG; ZERO ADDRESS-TYPE LABEL FLAG (LIKE: LABEL INY)
800 ; THIS IS FOR THE INLINE SUBROUTINE BELOW.
810 LDA SFLAG; SHOULD WE PRINT TO THE SCREEN
820 BEQ MX; IF NOT, SKIP THIS PART
830 JSR PRNTLINE; PRINT LINE NUMBER
840 JSR PRNTSPACE; PRINT SPACE
850 JSR PRNTSA; PRINT PC (PROGRAM COUNTER)."SA" IS THE VARIABLE.
860 JSR PRNTSPACE
870 MX LDA PLUSFLAG; DO WE HAVE A + PSEUDO OP
880 BEQ MOE4; IF NOT SKIP
890 JSR MATH; IF SO, HANDLE IT IN SUBPROGRAM "MATH"
900 MOE4 JMP FINDMN; LOOK UP MNEMONIC (OR, NOT FINDING ONE, IT'S A LABEL)
910 ; -------- EVALUATE ARGUMENT
920 EVAR LDA TP
930 BEQ TPIJMP; CHECK TYPE, IF 0, NO ARGUMENT
940 CMP #3; IF NOT TYPE 3, THEN CONTINUE EVALUATION
950 BNE EVGO
960 LDA #1; OTHERWISE, REPLACE 3 WITH 1 IN TP (TYPE)
970 STA TP
980 LDA LABEL+3; IS THERE SOMETHING (NOT A ZERO) IN 4TH POSITION
990 BNE EVGO; EVGO = ARGUMENT (IF NOT, THERE'S NO ARGUMENT,IT'S IMPLIED
1000 LDA #8; OTHERWISE, RAISE OP (OPCODE) BY 8
1020 ADC OP
1030 STA OP
1040 TPIJMP JMP TP1; AND JUMP TO TYPE 1 (SINGLE BYTE TYPES)
1060 EQLABEL LDA PASS; MOE4 FOUND IT TO BE A LABEL, NOT A MNEMONIC
1070 BEQ EQLAB1; ON PASS 1 WE DON'T CARE WHICH KIND OF LABEL IT IS SO WE
1080 LDY #255; GO DOWN AND STORE IT IN THE ARRAY (VIA EQLAB1)
1090 EVXl INY; BUT ON PASS 2, WE NEED TO DECIDE IF IT'S A PC ADDRESS TYPE
1100 LDA LABEL,Y; LABEL (LIKE: LABEL INY) OR AN EQUATE TYPE (LABEL = 15)
1110 BEQ GONOAR; SO IN THIS LOOP WE LOOK FOR A BLANK WHILE STORING THE
1120 STA FILEN,Y; LABEL NAME IN THE "FILEN" BUFFER. IF WE FIND A 0, IT'S
1130 CMP #32; A NAKED LABEL (NO ARGUMENT TO IT) WHICH CAUSES US TO PRINT
1140 BNE EVX1;OUT THAT ERROR MESSAGE (AT NOAR, IN EQUATE).OTHERWISE, WE FIND A
1150 INY; BLANK AND FALL THROUGH TO THIS LINE.
1160 LDA LABEL,Y; WE RAISE Y BY 1 AND CHECK FOR AN = SIGN.
1170 CMP #$3D
1180 BNE NOTEQ; IF NOT, IT'S A PC ADDRESS TYPE (SO SET LOCFLAG)
1190 imp INLINE; IF SO,WAS = TYPE SO IGNORE IT (ON PASS 2) ----------
1200 NOTEQ LDX #0
1210 STX LOCFLAG;(SHOWS PRINTOUT TO DO THIS TYPE OF LABEL ON SCREEN/PRINTER)
1220 TXA; PUT A ZERO IN AT THE END OF THE LABEL NAME (AS A DELIMITER)
1230 STA FILEN,Y; NOW WE HAVE TO MOVE THE ARGUMENT PORTION OF THIS LINE
1240 EVX5 LDA LABEL,Y; OVER TO THE START OF THE "LABEL" BUFFER FOR FURTHER
1250 BEQ EVX4; ANALYSIS (0 DELIMITER HERE)
1260 STA LABEL,X; WE CAN IGNORE THE PC LABEL (THIS IS PASS 2), BUT WE
1270 INX; NEED TO EVALUATE THE REST OF THE LINE FOLLOWING THAT LABEL.
1290 JMP EVX5;-------------___---
1300 EVX4 STA LABEL,X
1310 JMP MOE4; JUMP TO CONTINUE EVALUATION
1320 GONOAR JSR NOAR; PRINT NO ARGUMENT MESSAGE (A SPRINGBOARD);----------
1330 EQLAB1 JSR EQUATE; PUT LABEL AND IT'S VALUE INTO THE ARRAY (PASS 1)
1340 JMP MOE4; CONTINUE EVALUATION
1350 ;------------------ TRANSLATE ARGUMENT LABELS INTO NUMBERS
1360 EVEXLAB LDA BUFFER; IS THIS 1ST CHARACTER ALPHABETIC (>64)
1370 CMP #64
1380 BCS EVE1; IF SO, GO DOWN TO FIND ITS VALUE.
1390 LDA BUFFER+1; IF NOT, IT MUST HAVE BEEN A ( OR # SYMBOL
1400 INC BUFLAG; TO TELL ARRAY THAT ( OR # WAS FOUND (AND TO IGNORE THEM)
1410 EVE1 FOR #$80; SET 7TH BIT IN 1ST CHAR. (TO MATCH ARRAY STORAGE METHOD)
1420 STA WORK; SAVE IT HERE TEMPORARILY TO COMPARE WITH ARRAY WORDS
1430 JSR ARRAY; EVAL. EXPRESSION LABEL, SHIFTED 1ST CHAR.
1440 JMP L340; THEN CONTINUE ON WITH EVALUATION (AFTER VALUE IS IN "RESULT")
1450 ;--------------- IS ARGUMENT NUMERIC OR A LABEL
1460 EVGO LDY #0
1470 STY EXPRESSF; TURN OFF THE "IT'S A LABEL" FLAG
1472 ;------------------- SEE CHAPTER 11 FOR DESCRIPTION OF THIS ERROR TRAP
1473 ; (TRAP FOR NAKED MNEMONICS ERROR)
1474 LDA LABEL+3:CMP #32:BEQ GVEG:JMP L700; (TEST FOR "INC:" TYPE ERROR)
1480 GVEG LDA LABEL+4,Y; CHECK 5TH CHAR. (LDA NAME OR LDA 25) (THE "N" OR "2")
1490 CMP #65; IF LESS THAN 65 (ASCII FOR "A") THEN IT'S A NUMBER
1500 BCC EVM02A
1510 INC EXPRESSF; >65 = ALPHABETIC ARG (LABEL) SO RAISE THIS FLAG
1520 EVM02A STA BUFFER,Y; STORE 1ST CHAR. OF ARGUMENT IN "BUFFER" BUFFER
1540 LDA LABEL+4,Y; LOOK AT 2ND CHAR. IN THE ARGUMENT
1550 BEQ EVM03; IF ZERO, WE'RE AT THE END SO MOVE ON
1560 STA BUFFER,Y; OTHERWISE, STORE 2ND CHAR.
1570 CMP #65; IF LOWER THAN 65, DON'T RAISE LABEL-ARGUMENT FLAG
1580 BCC EVM02
1590 INC EXPRESSF; IF HIGHER, DO RAISE IT
1600 EVM02 INY; NOW MOVE REST OF ARGUMENT UP TO "BUFFER" BUFFER
1610 LDA LABEL+4,Y; LOOP TO MOVE THE ARGUMENT INTO THE BUFFER
1620 BEQ EVM03; EVM03 TAKES OVER AFTER END OF ARGUMENT IS REACHED
1630 STA BUFFER,Y
1640 JMP EVM02; RETURN FOR MORE ARGUMENT CHARACTERS.
1660 EVM03 DEY
1670 STY ARGSIZE; REMEMBER NUMBER OF CHARACTERS IN ARGUMENT
1680 LDA HEXFLAG; IF IT'S HEX, INDISK SUBPROGRAM ALREADY TRANSLATED IT FOR US
1690 BNE L340; SO GO ON TO EVALUATE ADDRESS MODE.
1700 LDA EXPRESSF; IF IT'S A LABEL (NOT A NUMBER) THEN GO TO THE ROUTINE
1710 BNE EVEXLAB; WHICH EVALUATES EXPRESSION (ARGUMENT) LABELS, "EVEXLAB"
1720 ; ---- CALCULATE ARGUMENT'S VALUE (IF IT'S A DECIMAL NUMBER)
1730 LDA #<BUFFER, MAKE "TEMP" POINTER POINT TO "BUFFER"
1740 STA TEMP
1750 LDA #>BUFFER
1760 STA TEMP+1
1770 LDY #0
1780 LDA BUFFER; IS 1ST CHARACTER HIGHER THAN 48 (ASCII FOR THE NUMBER ZERO)
1790 CMP #48
1800 BCS MCAL; IF SO, SKIP THIS PART
1810 CLC; IF NOT, THE 1ST CHARACTER MUST BE # OR ( ..... SO WE NEED TO
1820 INC TEMP; MAKE "TEMP" POINT 1 CHARACTER HIGHER IN "BUFFER" TO
1830 BCC MCAL; AVOID HAVING THE ASCII TO INTEGER SUBROUTINE THINK THAT THE
1840 INC TEMP+1; NUMBER STARTS WITH A # OR ( --- THAT WOULD MESS THINGS UP.
1850 MCAL LDA (TEMP),Y; NOW LOOK FOR THE END OF THE NUMBER: -----------
1860 BEQ MCALl; IT COULD END WITH A 0 (DELIMITER) OR
1870 CMP #41; WITH A ) RIGHT PARENTHESIS OR
1880 BEQ MCALl
1890 CMP #44; WITH A , COMMA (AS IN: 15,Y) OR
1900 BEQ MCALl
1910 CMP #32; WITH BLANK SPACE (AS IN: #15 ;COMMENT)
1920 BEQ MCALl
1930 INY; IF WE'VE NOT YET FOUND ONE OF THESE 4 THINGS, CONTINUE LOOKING
1940 JMP MCAL;----------------------------
1950 MCAL1 PHA; SAVE ACCUMULATOR
1970 PHA;SAVE Y REGISTER(BY NOW, Y IS POINTING AT THE SPACE JUST AFTER THE #)
1980 LDA #0; PUT DELIMITER ZERO INTO BUFFER JUST FOLLOWING NUMBER.
1990 STA (TEMP),Y
2000 JSR VALDEC;GO TO THE ASCII-NUMBER-TO-INTEGER-NUMBER-IN-"RESULT" ROUTINE
2010 PLA; RESTORE THE A AND Y REGISTERS
2040 STA (TEMP),Y; RESTORE "," OR ")" TO THE BUFFER (FOR THE ADDR. ANALYSIS)
2050 ;--------- ANALYZE THE ARGUMENT TO DETERMINE ADDRESSING MODE ------
2060 ; (THIS ESSENTIALLY AMOUNTS TO MODIFYING THE ORIGINAL OPCODE TO
2070 ; REFLECT THE CORRECT ADDRESSING MODE. ADJUSTMENTS TO THE OPCODE "OP"
2080 ; APPEAR RATHER FREQUENTLY FROM HERE ON. THEIR LOGIC WILL NOT BE
2090 ; COMMENTED. ADDING 4,8,16, OR 24 TO AN "OP" IS BASED ON THE
2100 ; RELATIONSHIPS WITHIN THE OPCODE TABLE (SEE CHAPTER 9 FOR EXPLANATION)
2120 L340 LDA BUFFER; 1ST CHAR. OF THE ARGUMENT (THE "#" IN LDA #15)
2130 CMP #35
2140 BEQ DIMMED; # SYMBOL FOUND (SO IMMEDIATE MODE). BRANCH TO SPRINGBOARD
2150 CMP #40; IS IT A "(" LEFT PARENTHESIS. IF SO, GO TO INDIRECT ADDR.
2160 BEQ INDIR
2170 LDA TP; IS IT A RELATIVE ADDR. MODE (LIKE BNE, BEQ).
2180 CMP #8
2190 BEQ REL; IF SO, GO TO WHERE THEY ARE HANDLED.
2200 CMP #3; ADD 8 TO OP AT THIS POINT IF IT'S A TYPE 3
2210 BNE EVM05
2220 LDA #8
2240 ADC OP
2250 STA OP
2260 JMP TP1; AND JUMP TO THE SINGLE BYTE TYPES (IMPLIED ADDRESSING)
2270 INDIR LDY ARGSIZE; HANDLE INDIRECT ADDRESSING-------------------
2280 LDA BUFFER,Y: LOOK AT THE LAST CHARACTER IN THE ARGUMENT.
2290 CMP #41; IS IT A ")" RIGHT PARENTHESIS
2300 BEQ MINDIR; IF SO, HANDLE THAT TYPE.
2310 LDA TP
2320 CMP #1; IF TYPE 1, ADD 16 AT THIS POINT TO OPCODE
2330 BNE MINDIR
2340 LDA #16
2360 ADC OP
2370 STA OP
2380 MINDIR LDA TP; TYPE 6 IS A JUMP INSTRUCTION
2390 CMP #6
2400 BEQ JJUMP; SO GO TO THE JUMP-HANDLING ROUTINE
2410 JMP TWOS; OTHERWISE, IT MUST BE A 2-BYTE TYPE SO PRINT/POKE IT.;-------
2420 DIMMED JMP IMMED; SPRINGBOARD TO IMMEDIATE MODE TYPES.
2430 ;------------------------------- HANDLE RELATIVE ADDRESS (BNE) TYPES
2440 REL LDA PASS; ON PASS 1, DON'T BOTHER, JUST INCREASE PC BY 2
2450 BNE MREL
2460 JMP TWOS
2470 MREL SEC; ON PASS 2, SUBTRACT PC FROM ARGUMENT TO GET REL. BRANCH
2480 LDA RESULT
2490 SBC SA
2500 PHA; SAVE LOW BYTE ANSWER
2510 LDA RESULT+1
2520 SBC SA+1
2530 BCS FOR; IF ARGUMENT > CURRENT PC, THEN IT'S A BRANCH FORWARD
2540 CMP #$FF
2550 BEQ MPXS
2570 JMP DOBERR
2580 MPXS PLA; OTHERWISE, CHECK FOR OUT OF RANGE BRANCH ATTEMPT
2590 BPL BERR; OUT OF RANGE (PRINT ERROR MESSAGE "BERR")
2600 JMP RELM; AND JUMP TO REL CONCLUSION ROUTINE
2610 FOR BEQ MPXS1; CHECK FORWARD BRANCH OUT OF RANGE
2630 JMP DOBERR
2640 MPXS1 PLA
2650 BPL RELM; WITHIN RANGE-----------------------
2660 BERR JMP DOBERR; PRINT "BRANCH OUT OF RANGE" ERROR MESSAGE
2670 RELM SEC; FINISH UP REL. ADDR. TYPE -------------------
2680 SBC #2; CORRECT FOR THE FACT THAT BRANCHES ARE CALCULATED FROM THE
2690 STA RESULT; INSTRUCTION FOLLOWING THEM: BNE LOOP:LDA 15 WOULD BE
2700 LDA #0; CALCULATED FROM THE PC OF THE LDA 15
2710 STA RESULT+1
2720 JMP TWOS; NOW GO TO THE 2-BYTE PRINT/POKE (WITH CORRECT ARGUMENT)
2730 ;------------------------------- CONTINUE ADDR. MODE ANALYSIS
2740 EVM05 LDY ARGSIZE
2760 LDA BUFFER,Y; LOOK AT LAST CHARACTER OF ARGUMENT
2770 CMP #44; IF IT'S NOT A COMMA, THEN THIS MUST BE A JUMP INSTRUCTION
2780 BNE JJUMP; SO GO TO THE JUMP-HANDLING ROUTINE
2800 JMP XYTYPE; OTHERWISE, IT MUST BE A X OR Y TYPE;---------------
2810 JJUMP LDA OP; HANDLE JMP MNEMONIC
2820 CMP #76; IF THE OPCODE ISN'T 76, IT'S NOT A JUMP
2830 BNE MEV; SO LOOK FOR SOMETHING ELSE
2840 JMP JUMP; NOW SPRINGBOARD TO THE JUMP-HANDLING ROUTINE.----------
2850 MEV LDA RESULT+1; IF HIGH BYTE OF RESULT ISN'T ZERO (ZERO PG. ADDR)
2860 BNE PREPTHREES; THEN GO TO THE 3-BYTE INSTRUCTIONS (LINE 400)
2870 LDA TP; OTHERWISE, IT'S ZERO PAGE MODE
2880 CMP #6; IF HIGHER THAN TYPE 6, IT'S AN ORDINARY 2-BYTE TYPE
2890 BCS TWOS; SO GO THERE.
2900 CMP #2; IF TYPE 2, ALSO GO THERE.
2910 BEQ TWOS
2920 LDA #4; OTHERWISE, ADD 4 TO OPCODE AND FALL THROUGH INTO TWO-BYTE TYPE
2940 ADC OP
2950 STA OP
2960 ;------------------------------- 2 BYTE TYPES (LIKE LDA 12)
2970 TWOS JSR FORMAT; PRINT/POKE OPCODE
2980 JSR PRINT2; THEN PRINT/POKE ARGUMENT
2990 JMP INLINE; AND FINALLY PREPARE TO FETCH NEW LINE OF SOURCECODE (2000)
3000 ;------------------------------- HANDLE JMP
3010 JUMP LDY ARGSIZE; IS IT JMP 1500 OR JMP (1500)
3020 LDA BUFFER,Y; ) AT THE END PROVES IT'S AN INDIRECT JUMP SO
3030 CMP #41
3040 BNE JUMO
3050 LDA #108; WE MUST CHANGE THE OPCODE FROM 76 TO 108
3060 STA OP
3070 JUMO JMP THREES; TREAT IT AS A NORMAL 3-BYTE INSTRUCTION
3080 ;------------------------------- IMMEDIATE ADDRESSING (# TYPE)
3090 IMMED LDA BUFFER+1
3100 CMP #°°; IS THIS A CHARACTER LOAD PSEUDO-OP LIKE: LDA #"A
3110 BNE IMMEDX
3120 LDA BUFFER+2; IF SO, PUT THE ASCII CHAR. INTO "RESULT" (ARGUMENT)
3130 STA RESULT
3140 IMMEDX LDA TP
3150 CMP #1
3160 BNE TWOS; IF IT'S TYPE 1, ADJUST OPCODE BY ADDING 8 TO IT.
3170 LDA #8
3180 CLC:ADC OP:STA OP
3190 JMP TWOS
3200 ;------------------------------- 1 BYTE TYPES
3210 TP1 JSR FORMAT; JUST POKE OPCODE FOR THESE, THERE'S NO ARGUMENT
3220 JMP INLINE; (LINE 1000)
3230 ;------------------------------- 3 BYTE TYPES
3240 PREPTHREES LDA TP; SEVERAL OPCODE ADJUSTMENTS (BASED ON TYPE)
3250 CMP #2
3260 BEQ PTT
3270 CMP #7; (LINE 430)
3280 BNE PT1
3290 PTT LDA OP
3310 ADC #8
3320 STA OP
3330 JMP THREES
3340 PT1 CMP #6
3350 BCS THREES
3360 LDA OP
3380 ADC #12
3390 STA OP
3400 THREES JSR FORMAT; PRINT/POKE OPCODE
3410 JSR PRINT3; PRINT/POKE 2 BYTES OF THE ARGUMENT (3000)
3420 ;------------------------------------- PREPARE TO GET A NEW LINE
3430 ;PRINT MAIN INPUT AND COMMENTS, THEN TO STARTLINE
3440 INLINE LDA PASS; ON PASS 1, IGNORE THIS WHOLE PRINTOUT THING.
3450 BNE NLOX1
3460 JMP JST
3470 NLOX1 LDA SFLAG; LIKEWISE, IF SCREENFLAG IS DOWN, IGNORE.
3480 BNE NLOX
3490 JMP JST
3500 NLOX LDA LOCFLAG; ANY PC ADDRESS LABEL TO PRINT
3510 BNE PRMMX1; NO LOC TO PRINT (RVS FLAG USAGE, FOR SPEED)
3520 LDA PRINTFLAG; PRINT TO PRINTER
3530 BEQ PRMM
3540 LDA #20
3560 SBC CURPOS; SUBTRACT CURRENT CURSOR POSITION
3570 STA A; MOVE THE CURSOR TO 20TH COLUMN ON THE SCREEN
3580 JSR CLRCHN; PREPARE PRINTER TO PRINT BLANKS
3590 LDX #4
3600 JSR CHKOUT
3610 LDY A
3620 BPL PRXM1
3630 LDY #2
3640 JMP PRMLOP
3650 PRXM1 LDA #32
3660 PRMLOP JSR PRINT;---------------- PRINT BLANKS TO PRINTER
3680 BNE PRMLOP; PRINT MORE BLANKS TO PRINTER;---------------
3690 JSR CLRCHN; RESTORE NORMAL I/O
3700 LDX #1
3710 JSR CHKIN
3720 PRMM LDA #20; PUT 20 INTO CURRENT SCREEN CURSOR POSITION
3730 STA CURPOS
3740 LDA #<FILEN; POINT "TEMP" TO PC ADDRESS LABEL FOR PRINTOUT
3750 STA TEMP
3760 LDA #>FILEN
3770 STA TEMP+1
3780 JSR PRNTMESS; PRINT LOCATION LABEL;----------
3790 PRMMX1 LDA #30; MOVE CURSOR TO 30TH COLUMN
3810 SBC CURPOS
3820 STA X; SAVE OFFSET FROM CURRENT POSITION (30-POSITION) FOR PRINTER
3830 LDA #30
3840 STA CURPOS; SET SCREEN CURSOR POSITION TO 30
3850 LDA PRINTFLAG; DO WE NEED TO PRINT BLANKS TO THE PRINTER
3860 BEQ PRMMFIN
3870 JSR CLRCHN; ALERT PRINTER TO RECEIVE BLANKS
3880 LDX #4
3890 JSR CHKOUT
3900 LDY X
3910 BEQ PXMX; HANDLE NO BLANKS (IGNORE)
3920 BMI PXMX; HANDLE TOO MANY BLANKS (>127) (IGNORE)
3930 LDA #32
3940 PRMLOPX JSR PRINT; PRINT BLANKS TO PRINTER FOR FORMATTING--------
3960 BNE PRMLOPX; PRINT MORE BLANKS----------
3970 PXMX JSR CLRCHN; RESTORE NORMAL I/O
3980 LDX #1
3990 JSR CHKIN;-------------------------------------------------
4000 PRMMFIN JSR PRNTINPUT; PRINT MAIN INPUT BUFFER (BULK OF SOURCE LINE)
4010 LDA BYTFLAG; IS THERE A < OR > PSEUDO-OP TO PRINT --------------
4020 BEQ PRXM; HANDLE < AND >
4030 CMP #1; 1 IN BYTFLAG MEANS <
4040 BNE M05
4050 LDA #60
4060 JMP PRMO
4070 M05 LDA #62; PRINT >
4080 PRMO JSR PRINT
4090 JSR PTP1; PRINT > OR <. PTP1 IS TO PRINTER----------
4100 PRXM LDA BABFLAG; IS THERE ANY COMMENT TO PRINT (SOMETHING FOLLOWING ;)
4110 BEQ RETTX; IF NOT, SKIP THIS.
4120 JSR PRNTSPACE; PRINT A SPACE-------- PRINT COMMENTS FIELD ----------
4130 LDA #59; PRINT A SEMICOLON
4140 JSR PRINT
4150 LDA #<BABUF; POINT "TEMP" TO THE COMMENTS BUFFER "BABUF"
4160 STA TEMP
4170 LDA #>BABUF
4180 STA TEMP+1
4190 JSR PRNTMESS; PRINT WHAT'S IN THE COMMENTS BUFFER
4200 RETTX JSR PRNTCR; PRINT CARRIAGE RETURN
4210 LDA ENDFLAG; IF ENDFLAG IS UP, JUMP TO THE SHUTDOWN ROUTINE
4220 BNE FINI
4230 JST JMP STARTLINE; OTHERWISE GO BACK UP TO GET THE NEXT SOURCE LINE.
4240 ;------------------------------------- THE END OF A PASS (1 OR 2)
4250 FINI LDA PASS
4260 BNE FIN; IF IT'S PASS 2, SHUT EVERYTHING DOWN.
4270 INC PASS; OTHERWISE, CHANGE PASS 1 TO PASS TWO (IN THE FLAG)
4280 LDA TA; PUT THE ORIGINAL START ADDR. INTO THE PC PROGRAM COUNTER (SA)
4290 STA SA
4300 LDA TA+1
4310 STA SA+1
4320 JSR CLRCHN; RESTORE ORDINARY I/0 CONDITIONS
4330 LDA #1
4340 JSR CLOSE; CLOSE INPUT FILE
4350 JSR OPEN1; OPEN INPUT FILE (POINT IT TO THE 1ST BYTE IN THE FILE)
4360 JMP SMORE; PASS 1 FINISHED, START PASS 2 (ENTRY POINT FOR PASS 2) ------
4380 ;------------------- SHUT DOWN LADS OPERATIONS AND RETURN TO BASIC -----
4390 FIN JSR CLRCHN; RESTORE NORMAL I/0
4400 LDA #1
4410 JSR CLOSE; CLOSE SOURCE CODE INPUT FILE
4420 LDA #2
4430 JSR CLOSE; CLOSE OBJECT CODE OUTPUT FILE (IF ANY)
4440 LDA PRINTFLAG; IS THE PRINTER ACTIVE
4450 BEQ FINFIN; IF NOT, JUST RETURN TO BASIC
4460 JSR CLRCHN; OTHERWISE SHUT DOWN PRINTER, GRACEFULLY.
4470 LDX #4
4480 JSR CHKOUT
4490 LDA #13; BY PRINTING A CARRIAGE RETURN
4500 JSR PRINT
4510 JSR CLRCHN
4520 LDA #4
4530 JSR CLOSE
4540 FINFIN JMP TOBASIC; RETURN TO BASIC
4560 ;------------------------------------- X OR Y ADDRESSING TYPE
4570 XYTYPE LDA BUFFER,Y; LOOK AT LAST CHAR. IN ARGUMENT
4580 CMP #88; IS IT AN X
4590 BEQ L720
4600 DEY; OTHERWISE, LOOK AT THE 3RD CHAR. FROM END OF ARGUMENT
4620 LDA BUFFER,Y; IS IT A ) RIGHT PARENTHESIS
4630 CMP #41
4640 BNE ZEROY; IF NOT, IT'S NOT AN INDIRECT ADDR. MODE
4650 JMP INDIR; IF SO, IT IS AN INDIRECT ADDRESSING MODE
4660 ZEROY LDA RESULT+1; CHECK HIGH BYTE OF RESULT (ZERO PG. OR NOT)
4670 BNE L680; ZERO Y TYPE
4680 LDA TP; ADJUST OPCODE BASED ON TYPE
4690 CMP #2
4700 BEQ L730
4710 CMP #5
4720 BEQ L730
4730 CMP #1
4740 BEQ L760
4750 L680 LDA TP
4760 CMP #1
4770 BNE L690
4780 LDA OP
4800 ADC #24
4810 STA OP
4820 JMP THREES
4830 L690 LDA TP
4840 CMP #5
4850 BEQ M6
4860 LDA #$31
4870 JSR P
4880 JMP L700
4890 M6 LDA OP
4910 ADC #28
4920 STA OP
4930 JMP THREES
4940 ;----------- PRINT A SYNTAX ERROR MESSAGE ------------------
4950 L700 JSR ERRING; RING ERROR BELL AND TURN ON REVERSE CHARACTERS
4960 JSR PRNTLINE; PRINT LINE NUMBER
4970 LDA #<MERROR; POINT "TEMP" TO SYNTAX ERROR MESSAGE
4980 STA TEMP
4990 LDA #>MERROR
5000 STA TEMP+1
5010 JSR PRNTMESS:JSR PRNTCR; PRINT THE MESSAGE
5020 JMP INLINE; GO TO THE GET-THE-NEXT-LINE ROUTINE
5030 ;------------------------------ CONTINUE ANALYSIS OF ADDR. MODE
5040 L720 LDA RESULT+1; MAKE FURTHER ADJUSTMENTS TO OPCODE
5050 BNE L780; NOT ZERO PAGE
5060 L730 LDA TP
5070 CMP #2
5080 BNE L740
5090 LDA #16
5110 ADC OP
5120 STA OP
5130 JMP TWOS
5140 L740 CMP #1
5150 BEQ L759
5160 CMP #3
5170 BEQ L759
5180 CMP #5
5190 BEQ L759
5200 L750 LDA #$32
5210 JSR P
5220 JMP L700
5230 L759 LDA #20
5250 ADC OP
5260 STA OP
5270 ; ---------- SEE CHAPTER 11 FOR EXPLANATION OF THIS ERROR TRAP ------
5271 L760 LDA BUFFER+2,Y:CMP #89:BNE ML760; --- ERROR TRAP FOR LDA (15,Y)
5272 LDA OP:CMP #182:BEQ ML760; IS THE MNEMONIC LDX (IF SO, MODE IS CORRECT)
5273 JMP L680; IF NOT, JUMP TO MAKE IT (LDA $0015,Y) ABSOLUTE Y
5274 ML760 JMP TWOS
5280 L780 LDA TP
5290 CMP #2
5300 BNE L790
5310 LDA #24
5330 ADC OP
5340 STA OP
5350 JMP THREES
5360 L790 CMP #1
5370 BEQ L809
5380 CMP #3
5390 BEQ L809
5400 CMP #5
5410 BEQ L809
5420 L800 LDA #$33
5430 JSR P
5440 JMP L700
5450 L809 LDA #28
5470 ADC OP
5480 STA OP
5490 JMP THREES; END OF ADDR. MODE EVALUATIONS AND ADJUSTMENTS
5510 ;-------------------- ERROR REPORTING FOR DEBUGGING (PRINTS PC)
5520 P STA A; WHEN YOU INSERT A "JSR P" INTO YOUR SOURCE CODE, THIS ROUTINE
5530 STY Y; WILL PRINT THE PC FROM WHICH YOU JSR'ED.
5540 STX X; AFTER AN RTS, THIS WILL REVEAL THE JSR ADDR.
5550 LDA #$BA; PRINT A GRAPHICS SYMBOL TO SIGNAL THAT THE PC IS TO FOLLOW
5560 JSR PRINT
5570 PLA; SAVE THE RTS ADDRESS (TO KEEP THE STACK INTACT)
5660 JSR OUTNUM; PRINT THE PC ADDRESS.
5670 LDA A; RESTORE THE REGISTERS.
5680 LDY Y
5690 LDX X
5720 CLEANLAB LDY #0; FILLS MAIN INPUT BUFFER ("LABEL") WITH ZERO. CLEANS IT.
5740 CLEMORE STA LABEL,Y
5760 CPY #80
5770 BNE CLEMORE
5790 ; ---------PRINT BRANCH OUT OF RANGE ERROR MESSAGE--------------
5800 DOBERR JSR PRNTCR; PRINT "BRANCH OUT OF RANGE" ERROR MESSAGE
5810 JSR ERRING
5820 JSR PRNTLINE; PRINT THE LINE NUMBER
5830 LDA #<MBOR; POINT "TEMP" TO THE ERROR MESSAGE "MBOR"
5840 STA TEMP; (MESSAGE BRANCH OUT OF RANGE, MBOR)
5850 LDA #>MBOR
5860 STA TEMP+1
5870 JSR PRNTMESS; PRINT THE MESSAGE
5880 JSR PRNTCR; PRINT A CARRIAGE RETURN AND
5890 JMP TWOS; BUNGLE AS AN ORDINARY 2-BYTE EVENT (TO KEEP PC CORRECT)
5910 FILE EQUATE
Program 3-2. Eval, Apple Modifications
To create the Apple version of Eval, change the following lines in Program 3-1:
25 SETUP JMP EDITSU; START THE WEDGE
40 LDY #50
200 CMP #$A0
310 CMP #$A0; IF NO SECOND BLANK SPACE
4282 SEC; SAVE THE LENGTH OF THE CODE
4283 LDA SA; FOR THE THIRD AND FOURTH
4284 SBC TA; BYTES OF THE BINARY
4285 STA LENPTR; FILE CREATED BY THE
4286 LDA SA+1; D PSEUDOP
4287 SBC TA+1
4288 STA LENPTR+1
5770 CPY #255
Program 3-3. Eval, Atari Modifications
To create the Atari version of Eval, change the following lines in Program 3-1:
10 ;ATARI MODIFICATIONS--"EVAL"
;0 TOP JMP EDIT
40 START L.DA #0
50 STA 82
60 LDY #48
70 STRTLP STA OP.Y
90 BNE STRTLP
100 LDA #<: TOP
110 STA MEMTOP
120 STA ARRAYTOP
130 LDA #`--TOP
140 STA MEMTOP+1
150 STA ARRAYTOP+1
160 LDA #1
170 STA HXFLAG
180 JSR CLRCHN
190 LDA RAMFLAG
200 BNE SMOKE
210 LDY #0
220 LDX ARGPOS
240 STMO LDA BABUF,X
250 CMP #155
260 BEQ STMT
270 STM3 STA FILEN,Y
300 JMP STMO
310 STM1 STY FNAMELEN
320 JSR OPEN1
470 LDA #160
4271 LDA SA
4272 STA LLSA
4273 LDA SA+i
4274 STA LLSA+1
4351 LDA RAMFLA6
4352 BNE OVEROPEN
4353 JSR OPEN1
4360 OVEROPEN JMP SMORE
4415 LDX #2:JSR CHKOUT:LDA #O:JSR PRINT:JSR
5550 LDA #160
5910 FILE D:EQUATE.SRC
Return to Table of Contents | Previous Chapter | Next Chapter