Equate and Array:
Data Base Management
The job of setting up an array in machine language is simpler than you might imagine. The subprograms Equate and Array build and access a data base.
There are two basic ways to go about storing information: in fixed or in variable length fields. (A field in data base management means a single item, such as a single label name in LADS.) Fixed fields are easier to search, modify, and sort. Variable length fields save memory space. LADS uses variable length fields so the label table will take up as little space as possible.
A fixed field label system of managing data assigns a specified size in bytes for each item. If we had wanted to use this method of data storage for LADS' labels, we could have made a rule that label names cannot be larger than ten letters long. This would obviously make it simpler to manage the data.
However, then any label, even short labels, would always take up ten bytes. That would use up memory rather inefficiently. Instead, LADS allows labels to be of any length. If you are like me, the labels that you will think up naturally (without any restrictions imposed on your imagination) will normally average about five characters in length. Some will be longer, some shorter, but the average label will take up five bytes. Two bytes will be attached to each label to hold the integer number value which the label stands for. So, the average LADS variable (label name plus two-byte integer) takes up seven bytes. However, these variable length fields use up about 40 percent less memory when you consider that fields fixed at ten bytes would always take up ten bytes plus the two-byte number, never less.
Sons, Daughters, Clones
LADS itself is, of course, an ML program. You can have LADS object code assemble the LADS source code to disk or somewhere in RAM memory. This would create a new version of the assembler. If you'd made any changes to the source code, it would be an offspring, a son or daughter of LADS. If you didn't change the source code, you'd have created a clone, but the start address would differ.
LADS is about 5K long and uses 402 different labels. When it assembles itself from its own source code, it builds a label table which is 2851 bytes large. If it had fields fixed at ten bytes, the label table would be 4824 bytes large.
Why worry? It's true that the label table matters only during the actual assembly process. As soon as object code has been created and LADS returns to BASIC, the label table has served its purpose and can be tossed out like an eggshell after the egg is in the pan.
There are two good reasons for conserving memory: (1) the environment and (2) interactive freedom. Picture this: While assembling itself (or a comparably large program), LADS uses up about 8K of memory-5K for itself, perhaps 3K for the label table that builds down from the bottom of the assembler. And if you've chosen the option of assembling object code to RAM memory, add another 5K for the object code (the resulting ML program). A total of 13K. In some computers, this represents a significant bite out of the available memory.
What's more, LADS is supposed to be interactive. You are to have the psychological freedom you have with BASIC, to change things, to experiment, and then to quickly assemble and test the result. This means that you need space to write your source program (in RAM where a BASIC program is normally written). Perhaps you'll want a monitor extension in RAM too, like "Micromon" or "Supermon" or some other collection of ML utilities which permit single-step analysis of ML object programs, and other tools which are useful when debugging object code. And you might want "BASIC Aid" or "POWER" or some BASIC auto numbering, and other BASIC aids to manipulate the source code. You might want two different versions of your object code in RAM simultaneously so you can compare them in action.
The Programming Environment
All of these options require available RAM. If you can have them all in memory at once, you've got a better environment for developing an ML program. You won't always need to wonder if it's worth loading in a certain routine or utility: They're all there and ready to go. All your tools are at hand. This is a more efficient way to program. Tools that are out of reach are usually tools left unused.
Second, you want as few restrictions as possible when working with ML. You don't want to concern yourself about the length of each label name. Is it short enough? Does it duplicate a similar name? Eliminating these questions, too, is part of the interactivity, the mental freedom that comes with a smoothly running, efficient program development system. Variable length labels promote both effective memory conservation and an efficient programming environment.
The Equate subprogram starts off with one of those LDY #255 initializations. Remember that we don't always want to LDY #0 before a loop. There are times when the first event is the zeroth event. This is one of those times.
Line 40 sets Y to 255 so the INY in line 50 will make Y = 0. This allows us to LDA LABEL,Y and receive the first character in the buffer called LABEL. If we had set Y=0, the INY would have forced us to look at the second character in the buffer. Why not put the INY lower in the loop somewhere? That way, we would load in the first character the first time through the loop.
Obviously we can't INY just before the BNE in line 90. That would branch depending on the condition of Y itself, not on the item in A (which is our intention). For the same reason, we can't put it just before the BEQ in line 70. The only other safe place for it would be in a line between 70 and 80. That wouldn't do any damage to the branches because the CMP will reset the flags and the following BNE will act correctly.
This loop isn't moving characters from one buffer to another or anything. Its sole purpose is to count the number of characters in a label name, to find the length of the label. Y is the counter.
While locating Y in a line 75 would work correctly, it would be less clear what the loop is accomplishing. In cases like this, you have to decide where your personal priorities lie: Do you want to emphasize the function of a routine in a way that's more easily understood, or do you want to emphasize a uniform style of coding loops? If you prefer to always start such loops with LDY #0, by all means, go ahead. But that LDY #255 serves to alert you that this loop is a special kind of loop. If you come back later to modify a program, such signals can be helpful.
Once the length of our label is discovered, we add 2 to it by INY INY, to make room for the two-byte integer which will be attached to the label in our array. Each label stands for a number. And any legal number in ML can be stored within two bytes as an integer between 0 and 65535 ($0000-$FFFF).
Equate is called upon only during pass 1. On pass 1, the assembler puts each label into the array and attaches the two-byte integer onto the end of the word. So Equate's first job is to find out how much room to make in the array for each new label it comes upon. It makes room by lowering the MEMTOP variable by the length of the label name, plus two.
Building the Array Downward
SUBMEM moves our pointer down to make room for a new label. When SUBMEM is finished (200), the array is larger by the size of the new word we're adding to it, plus two bytes for the value of the word. The array is thus expanded, lowered.
Now we can store the label in the array. The first letter of each label in the array is special. It's shifted. That is, we add $80 (128 decimal) to the normal ASCII code value of the character. This is the same as setting the seventh bit.
If the label is "addnum," we want to store it as "Addnum" so that when we later search through the array, we can locate the start of each new label. The shifted letter will be our delimiter, separating the different labels. With fixed length fields, we wouldn't need a delimiter at all-each label would be exactly the same size as every other label. But our labels can vary in length, so we have to know where one begins and another ends.
The array will look like this (the xx is the two-byte value of each label):
What exactly does it mean to say that a letter is shifted? In the ASCII code for alphabetic, numeric, punctuation (! or . or ,), and symbolic (# or % or *) characters, everything is assigned a code number which is lower than 128. Above 128 are the uppercase versions of letters, etc. Hence, above 128, the characters are shifted. For the purposes of ML, a shifted character is something with an ASCII code value greater than 127. It has the seventh bit set in its byte: 10000000. That leftmost bit would always be up in any shifted character. This phenomenon makes it easy to distinguish between shifted and unshifted characters. We can just LDA CHARACTER and then BMI (branch if seventh bit up) or BPL (branch if seventh bit down). The subprogram Array will make good use of this clue.
For now, all we want to do is shift the first character before we store it into the array. We just set up the seventh bit. If that's the same as adding $80 to a character, why not simply ADC $80 instead of EOR $80 (230)? With EOR we get a 1 if either of the compared bits is set. We get a 0 if both bits are 1 or if both bits are 0. The only way we get a 1 is if one of the bits is 0 and the other bit is 1. Any other situation results in a 0. Look at a bit comparison:
1 EOR 1 = 0
0 EOR 0 = 0
1 EOR 0 = 1
Consequently, EOR $80, with the $80 (binary 10000000) acting as a mask, will leave all the bits in the Accumulator unchanged, but will set the seventh bit. The main reason to use EOR is that we don't have to bother with clearing the carry (CLC) as we normally would prior to any addition.
After we store the shifted first letter in what is currently the lowest position in the array, we INY. This serves two purposes: It points us to the second character in the label word and also points us to the second space from the bottom of the array (where the second character of the label word belongs).
Address or Equate?
Now we load the second character and check if it's a space (260-280). We might be dealing with a one-character-long label, like P. We've got to check for this eventuality. Finding such a short label, we would jump down to see if there's an = sign. But if the label is more than one character long, we store the second letter in the array (290) and jump back up to fetch and store the third and any additional letters in the label name.
The essential thing to notice here is that a space is our delimiter in the buffer-letting us know when we've reached the end of the label word. And after finding a space, we are then prepared to distinguish between the two types of labels: PC and equate.
We compare the character following the space to $3D (this is the = sign). If it is an = sign, we branch to the routine which assesses the argument following the equals sign (is it hex? is it decimal?). Otherwise, we go through this BEQ to the routine which handles PC-type labels (Program Counter types like: LABEL LDA 15, where the label indicates a location within the assembled program).
Storing the value of this kind of label is pretty simple: We just put the SA into the array. SA is the variable which always holds the current address during an assembly. But one thing remains to be done before we can return to the Eval subprogram to evaluate the LDA 15 part of this line. We've got to wipe out the word LABEL which precedes the LDA 15. Eval wouldn't know how to evaluate it. It's not a mnemonic.
After loading LABSIZE (the length of the label) into X, we load Y with 0. Y will point to the first space in the buffer, while X will count down until we've covered over the word LABEL (430).
Removing an Address Label
We load the leftmost part of the mnemonic/argument pair (the L of LDA is first), and we store it in the leftmost space in the buffer. In other words, the L of LDA covers up the L of LABEL. We continue with this process until we've loaded in a 0 and have therefore replaced LABEL LDA 15 with LDA 15, whereupon we store the final 0 as a delimiter and can return to Eval (510).
This next subroutine, NOAR (520), isn't in any sequential relationship to the other routines. It just happens to be here. It could be anywhere else in LADS just as easily. Its function is to ring the error bell and point TEMP to the message NAKED LABEL and then print that error message. It handles those cases when a programmer forgot to put anything after a label:
100 LABEL =
If we're not dealing with a PC-type label, though, we come here to store an equate label like LABEL = $22 (590) into the label array. We need to store Y first (in the variable LABPTR) so we can remember where in our array to put the value, the number following the equals sign. Remember that we've already stored the label name. What we need to do now is to put the value in the two bytes just following that name. When we arrive at this subroutine, Y is holding the correct offset from MEMTOP, the correct distance up in memory, from the bottom of the array to store the value.
There are now two possibilities. We are dealing with either a decimal number or a hex number. Hex numbers are translated by Indisk, the input subprogram, as they flow in from a disk file or RAM memory source code. So a hex number is already in the RESULT variable, waiting to be stored in the array.
But decimal numbers aren't translated as they come in. What's more, they arrive in ASCII form and must be converted into an integer by the subprogram Valdec.
We check the HEXFLAG to see if it's a hex number (610). If so, we can just put RESULT into the array and return to Eval (750).
But if it's a decimal number, we add the value of Y + 3 to the start-of-buffer address and point TEMP to the first character in the number we need to evaluate. We have to add this three to Y because the expression "space-equals sign-space" takes up three bytes. If we add this to the start of the buffer address, we're pointing to the first character in the number, pointing to the 1 in an example like: LABEL = 15.
Then we JSR to VALDEC, which looks at the number pointed to by TEMP and translates it from ASCII to an integer and puts the answer in the two-byte variable RESULT.
After this, we go through the same process as with hex numbers described above. The RESULT is transferred to the array, we pull off the two-byte RTS left on the stack (when we JSRed here from the Eval subprogram), and then jump back into Eval at INLINE, the place where a new line is pulled in from disk.
The Array subprogram is essentially a search routine. It looks up a label's name in the array that was built by the Equate subprogram. When it finds a match, it puts the integer value of the array word into the variable RESULT. In effect, Array replaces a label with its number. Here's an example fragment of source code:
10 * = 864
100 NAME = 2
110 LABEL = 15
120 START LDA LABEL
On pass 1, Equate would store "Start864Labell5Name02" into the array. The LADS label array builds down from the location of the start of LADS object code in memory. That is, the first part of LADS itself would be right above Name02. Line 120 contains two labels, START and LABEL. However, Equate ignores any labels which are not the first word in a given line. It only stores labels when it comes across the line in which they are defined. Any label being defined will be the first item in a given line. And if they are defined twice in the source code, that's an error.
(Note that, in the example of array storage above, Start864 is for illustration only. The number 864 is stored as a two-byte integer, not as 864, the ASCII characters we can read.)
While Equate ignores any label which is not the first thing on a line, Array ignores any label that is the first thing on a line. In the example above, Array would pay no attention to any of the labels except LABEL in line 120. It's Array's job to evaluate expression labels. An expression label is one that is used in an expression, one that is used as the argument of a mnemonic.
Array Works on Both Passes
Nevertheless, Array must operate on pass 1 as well as on pass 2. This is because pass 1 must keep an accurate PC, an accurate Program Counter. For Equate to store the correct number for labels, of the address (PC) type (like START in the example above), it must be able to find out precisely where in memory a given line is to be assembled. It must know that START is located at 864.
This problem derives from Zero Page addressing. LDA 15 takes up only two bytes in memory when assembled. LDA 1500 takes up three bytes. If labels were used in place of 15 and 1500 in these instructions, we must know whether to raise the PC by two or by three. So Array must look up all arguments on pass 1 to decide how much to increment the PC. (This PC, or Program Counter, is held in the LADS variable SA.)
In line 30 where Array begins, it moves the "bottom-of-LADS" (top of array) address from its permanent storage place, the variable ARRAYTOP, to the dynamic, changing pointer PARRAY. PARRAY will be lowered frequently as it points us down through the entire array.
Then we JSR to DECPAR which is the subroutine that lowers the PARRAY pointer by 1. And we stuff a $FF into the flag called FOUNDFLAG (90). This is a simple way to test if we've found our match. If we do find a match, as we'll soon see, we INC FOUNDFLAG. This means that FOUNDFLAG can more easily be tested in the way we want to test it. If it gets INCed once, it will be 0. INCed twice, it will be 1. INCed twice (or more) would mean that a label exists more than once in the array. That's an error, a redefined label, and we'll want to alert the programmer. Putting $FF into FOUNDFLAG thus allowed us to use BEQ to test for this error.
Checking for the Bottom
But all that comes later. The primary routine in Array starts with STARTLK (100), and oddly enough, the first thing we do is check to see if we're at the bottom of the array. The Equate subprogram always leaves the variable MEMTOP pointing to the bottom of the array. So, by subtracting our current position in the array (PARRAY) from the bottom of the array (MEMTOP), we can tell if we've finished looking through the array. If PARRAY is lower than MEMTOP, the carry will remain set, and we will then BCS down to the all-finished routine, ADONE.
Otherwise, we've got to keep on looking. Remember that Array must look through the entire array each time; even after it finds a match, it must continue looking for another match. This is the only way we can detect duplicated labels.
Array has to accomplish several things at once. It's got to point to the current position in the array, keep track of how large a given label is, and check each letter of each word. The chip registers will all be busy: A holds characters for checking, X keeps count of how large each label is, and Y (working with PARRAY) keeps track of our current position. Here, in line 160, we set X to zero.
Then we lower PARRAY by two to get past the number part of a label stored in array (170-230). We want to get past the 99 in /Label99/. Some of the stored numbers will have their seventh bit set; they'll be larger than 127. So we've got to jump over every stored number since the set seventh bit is our test to see if we've come upon the first character in a label name. We don't want numbers masquerading as label name delimiters.
At last we look at a character (260), and if the seventh bit is set, we BMI down to FOUNDONE. If it's not the start of a label name, we decrement PARRAY by 1 and jump up to LPAR to look at the next letter lower in memory within the array. Notice that we also raise the X (label length) counter (320). By the time we've found a shifted seventh bit indicating the start of a label name, X will hold the correct length of the name.
Let's pause a minute to look at how a double decrement works (280-310). If, upon loading the low byte of PARRAY, the zero flag is set, we would be forced to lower the high byte of PARRAY (PARRAY+ 1 in line 300). If the low byte isn't yet lowered to zero, however, we can just lower the low byte and ignore the high byte (310). Note that a zero in the low byte requires lowering both the high and low bytes. Correctly decrementing $8500 would result in $84FF, lowering both bytes, while a correct decrement of $8501 would just lower the low byte: $8500.
Once we have located a set seventh bit, thus locating the start of a label name, we come to the FOUNDONE subroutine (350). Here we must first store PARRAY into the temporary holding variable PT so we can remember exactly where the label name begins. Then we reload A with the first character of the label (390) and compare it against the first character of the label we're looking for. That first character was previously in the variable WORK just before we came to Array from Eval.
If these first characters match, we go to LKMORE to check the rest of the word for a full match. If not, we go to STARTOVER.
In LKMORE, we first raise X to be the correct length of the current array label under examination. Then we save it in the variable WORK+ 1. We've got to save it at this point because now X will serve as the counter of the source label length. The source label is the word we're looking for, the label from the source code we're trying to find a match to.
The fact that some labels will be like (LABEL),Y or #LABEL (having a ( or # as their first character) is a potential source of confusion to the Array search routine. To eliminate this confusion, whenever a ( or # is encountered during the Eval subprogram, a special flag, BUFLAG, is raised. That makes it easy for us to skip over them here by raising the Y offset (490) if necessary.
Paradoxically, we simply INY again, right after this. That's because we want to point to the second character in the label (we got this far because the first characters matched). Nevertheless, the combination of INY and DECPAR (490-500) effectively takes care of the ( or # situation and makes this INY point to the second letter of the label proper.
The LKM1 loop compares the entire rest of the source label against the array label (520-600). There are three ways, and only three ways, for us to get out of this loop. We can come upon a zero, which would surely be the end of the label in the buffer (the source label). A zero always means the end of a line of source code. Or we can come upon a character which is lower than 48. That includes things like left parentheses and commas in the ASCII code. Something like the comma in LDA LABEL,X would signal the end of the source label. (Checking for characters lower than 48, however, doesn't exclude numbers. We can still check for such legal labels as: LDA LABEL12.)
The Third Exit
The third way to exit this loop is when we fail to find a character match in the labels. Any point at which this happens, we "fall through" line 600-these characters do not BEQ, they're not equal. If they are equal, we go back up to check the next pair of characters. Notice that X continues to count the length of the words (580). In effect, it is counting the length of the source label (we already know the length of the array label and have it safely stashed away in the variable WORK+1).
If we leave this loop with a match, it will be a zero or a comma or right parenthesis in the source label that causes us to leave. X will then be holding the length of the source label. It's possible that we'll find an apparently "perfect match" which isn't, in fact, a match at all. For example, LABEL (as the array label) and LABE (as the source label) would appear to this LKM1 loop as a perfect match. The only way we have of knowing that they do not really match is to compare their lengths.
If we fail to find a match, STARTOVER (620) just restores the correct array location of PARRAY (pointing at the first character in the label that just failed), and then we lower PARRAY by 1 (660) and jump back up to the STARTLK routine. STARTLK will also lower PARRAY by 1. This double lowering of PARRAY moves it past the number stored in the two bytes at the end of the next label down, thus preparing us to start the comparison process all over again.
On the other hand, if we did find a match, we go to FOUNDIT (950). Right off the bat, we check to see if the current value of X (length of the source label) matches the previously stored value of X (length of the array label). If they don't match, we've got that LABEL LABE situation, and we STARTOVER.
If everything checks out, though, we've got an authentic match. We raise the FOUNDFLAG. If this is the first match, FOUNDFLAG goes up from $FF to $00. That's fine. There should be one match. If, however, FOUNDFLAG is higher than 0, it means we've found more than one match, and we JSR to DUPLAB where the "duplicated label" error message is printed out (1360).
With or without this message, we next compensate for the ( or # symbols which might be at the start of a source label and then load in the low byte of the number stored just above the array label. We put this byte into RESULT and put the high byte into RESULT+1. When we arrive here at FOUNDIT, the Y Register is pointing just past the end of the label. In other words, Y is pointing at the number stored with the label in the array. This is because we left the LKM1 loop when we got to the end of the label.
Here's where we make the adjustments for two of our pseudo-ops: > < and +. If BYTFLAG is set, it means that < or > was used to request the low or high byte of a label. LDA #<LABEL requests the low byte (and Eval will only deal with low bytes in the # Immediate addressing mode). The label's low byte is already in the low byte of RESULT, so we need do nothing. But BYTFLAG is a special kind of flag. It has three states rather than the normal two (set or clear, up or down) states. If it contains a 2, this signals that the #>LABEL pseudo-op was used, requesting the high byte of the label. To do this, we need to put the high byte of RESULT into the low byte of RESULT (1140-50). That's it.
PLUSFLAG signals a + pseudo-op like LDA LABEL+25. The amount we're supposed to add to LABEL (the 25) is already stored in the variable ADDNUM (by a subroutine in the Indisk subprogram). All we have to do here is add ADDNUM to the value in RESULT (1180-1240).
When these two pseudo-ops have been taken care of, we return to STARTOVER and keep looking for duplicated labels if we're on pass 1. On pass 1, we aren't allowed to leave the Array. On pass 2, however, it's not necessary to repeat this checking or to repeat the error messages, so we RTS, which sends us back to the Eval subprogram.
We've successfully put the value of the source label into RESULT. Now the Eval subprogram can go on to figure out the addressing mode, finish up by POKEing in the opcode and the argument, and then pull in the next line of source code.
But what if we didn't find any match to the source label and we've gone through the entire array? This can mean two things, depending on which pass we're on. On pass 1, it's harmless enough. It could well mean that the label hasn't yet been defined:
110 BNE FORWARDLOOP
130 FORWARDLOOP LDA 15
On the first pass, the label FORWARDLOOP will not be in the array until line 130. Nevertheless, the Array subprogram will search for it in line 110. And it won't find it. But so what? On pass 1, we can just ignore this failure to find a match and RTS back to Eval.
It would be a serious error, though, if the label could not be found in the array on pass 2. It would be an "undefined label" error.
When a Label Was Never Defined
Both of these possibilities are dealt with in the subroutine ADONE (690-940). If FOUNDFLAG has the seventh bit set, that means that it's still holding the $FF we put there at the very start of Array. We never found the match. We check the PASS, and if it's pass 2, we print the line number and the NOLAB error message "undefined label."
Then, no matter which pass it is, we still want to keep the program counter straight, or all the rest of the assembly will be off. The problem is that an undefined label doesn't give us the answer to the question: Is this a three-byte ordinary address or a two-byte zero page address? Is it LDA 15 or LDA 1500? Should we raise the PC by two or by three? If we raise it the wrong amount, any future reference to address-type labels will be skewed. Here's why:
100 * = 800
110 LDA LABEL; this label is undefined
120 ADDRESS INY; what is the location of ADDRESS here?
If LABEL is in zero page, ADDRESS = 802. If LABEL is not zero page, ADDRESS = 803. We should try to get this right on pass 1. Pass 2 depends on pass 1 for correct label values, including address-type labels. Even if a label is not yet defined, we should still try to raise the program counter by the correct amount.
In Eval there are routines called TWOS and THREES. TWOS raises the PC by two bytes for Zero Page and other two-byte-long addressing modes like LDA #15. THREES handles three-byte-long modes like Absolute addresses, etc. It's here in the Array subprogram, however, that we have to decide which of these routines to jump back to in Eval.
Branches like BNE and BEQ will often be undefined during pass 1 because the program is branching forward. We'll want to go to TWOS if there's an undefined label following a branch instruction. All branches are type 8, and we can easily check for them by LDA TP:CMP #8 (860). The other possible TWOS candidate is one of the > or < pseudo-ops. BYTFLAG signals one of them.
The # Immediate addressing mode is not tested for, so this adjustment isn't foolproof. The assumption is that any undefined label is essentially a fatal error and that there will have to be a reassembly. Most undefined labels are considered to be three-byte instructions and we JMP THREES (920).
This clarifies why LADS cannot permit the definition of a Zero Page address within the source code. All Zero Page address labels must be defined at the start of the source code, before any actual assembly takes place. Without this rule, our "yet-undefined-label" routine (690-930) will treat them, incorrectly, as three-byte address modes. It can recognize only branches and > < pseudo-ops as two-byte modes. Any other label that's not defined will be seen as a three-byte type.
Program 4-1. Equate
10 ; "EQUATE" EVALUATE LABELS
20 ; COULD BE EITHER PC (ADDRESS) TYPE OR EQUATE TYPE. STORE IN ARRAY.
25 ; FORMAT--NAME/2-BYTE INTEGER VALUE/NAME/2-BYTE VALUE/ETC...
40 EQUATE LDY #255; PREPARE Y TO ZERO AT START OF LOOP
50 EQ1 INY; Y GOES TO ZERO 1ST TIME THROUGH LOOP
60 LDA LABEL,Y; LOOK AT THE WORD, THE LABEL
70 BEQ NOAR; END OF LINE (SO THERE'S A NAKED LABEL, NOTHING FOLLOWS IT)
80 CMP #32; FOUND A SPACE, SO RAISE Y BY 2 AND SET LABEL SIZE (LABSIZE)
90 BNE EQ1; OTHERWISE, KEEP LOOKING FOR A SPACE.
120 STY LABSIZE
130 ;-------------------- LOWER MEMTOP POINTER WITHIN ARRAY (BY LABEL SIZE)
140 SUBMEM SEC; SUBTRACT LABEL SIZE FROM ARRAY POINTER TO MAKE ROOM FOR LABEL
150 LDA MEMTOP
160 SBC LABSIZE
170 STA MEMTOP
180 LDA MEMTOP+1
190 SBC #0
200 STA MEMTOP+1;--------------
205 ;SHIFT 7TH BIT OF 1ST CHAR. TO SIGNIFY START OF LABEL'S NAME
210 LDY #0
220 LDA LABEL,Y
230 FOR #$80
240 STA (MEMTOP),Y; STORE SHIFTED 1ST LETTER
250 EQ3 INY
260 LDA LABEL,Y; IF SPACE, STOP STORING LABEL NAME IN ARRAY.
270 CMP #32
280 BEQ EQ2
290 STA (MEMTOP),Y; OTHERWISE, PUT NEXT LETTER INTO ARRAY &
300 JMP EQ3; CONTINUE.
310 EQ2 INY; NOW CHECK FOR = (SIGNIFYING EQUATE TYPE) (LABEL = 15)
320 LDA LABEL,Y
330 CMP #$3D; IF EQUATE TYPE, GO TO FIND ITS VALUE.
340 BEQ EQUAL
350 DEY; OTHERWISE, IT'S PC TYPE (LABEL LDA 15)
360 LDA SA; SO THE PC VARIABLE (SA) CONTAINS THE VALUE OF THIS LABEL
370 STA (MEMTOP),Y; STORE IT RIGHT AFTER LABEL NAME WITHIN ARRAY.
390 LDA SA+1
400 STA (MEMTOP),Y
410 LDX LABSIZE; NOW, USING LABELSIZE AS INDEX, ERASE THE PC-TYPE LABEL
420 DEX; FROM THE BUFFER. FOR EXAMPLE, (LABEL LDA 15) NOW
430 LDY #0; BECOMES (LDA 15). THE LABEL NAME IS COVERED OVER
440 EQ5 LDA LABEL,X; TO PREPARE THE REST OF THE LINE TO BE ANALYZED
450 BEQ EQ4; NORMALLY BY EVAL.
460 STA LABEL,Y
490 JMP EQ5
500 EQ4 STA LABEL,Y
510 RTS; RETURN TO EVAL -------------------------
520 NOAR JSR PRNTCR:JSR PRNTLINE;NAKED LABEL FOUND (NO ARGUMENT) SO
525 JSR ERRING
530 LDA #<NOARG; RING BELL AND PRINT NAKED LABEL ERROR MESSAGE.
540 STA TEMP
550 LDA #>NOARG
560 STA TEMP+1
570 JSR PRNTMESS:JSR PRNTCR
580 JMP EQRET; RETURN TO EVAL-----------------------
585 ;--------------- HANDLE EQUATE TYPES HERE (LABEL = 15)
590 EQUAL DEY
600 STY LABPTR; TELLS US HOW FAR FROM MEMTOP WE SHOULD STORE ARGUMENT VALUE
610 LDA HEXFLAG; HEX NUMBERS ALREADY HANDLED BY INDISK ROUTINE, SO SKIP OVER.
620 BNE FINEQ; HEX FLAG UP, SO GO TO EQUATE EXIT ROUTINE BELOW.
630 INY; OTHERWISE, WE NEED TO FIGURE OUT THE ARGUMENT (LABEL = 15)
640 INY; THERE ARE THREE CHARS. ( _ ) BETWEEN LABEL & ARGUMENT, SO
650 INY; INY THRICE.
660 STY WORK+1; POINT TO LOCATION OF ASCII NUMBER (IN LABEL BUFFER)
670 LDA #<LABEL; SET UP TEMP POINTER TO POINT TO ASCII NUMBER
690 ADC WORK+1
700 STA TEMP
710 LDA #>LABEL
720 ADC #0
730 STA TEMP+1
740 JSR VALDEC; CALCULATE ASCII NUMBER VALUE AND STORE IN RESULT
750 FINEQ LDY LABPTR; STORE INTEGER VALUE JUST AFTER LABEL NAME IN ARRAY
760 LDA RESULT
770 STA (MEMTOP),Y
780 LDA RESULT+1
800 STA (MEMTOP),Y
810 EQRET PLA;PULL OFF THE RTS (FROM EVAL) AND JUMP DIRECTLY TO INLINE
820 PLA; IGNORING ANY FURTHER EVALUATION OF THIS LINE SINCE EQUATE TYPE
830 JMP INLINE; LABELS ARE FOLLOWED BY NOTHING TO EVALUATE
840 FILE ARRAY
For the Atari version of Equate, change line 840 to: 840 .FILE D:ARRAY.SRC
Program 4-2. Array
10 ; "ARRAY" LOOKS THROUGH LABEL TABLE AND PUTS VALUE IN RESULT.
20 ; (USED IN BOTH PASS 1 AND PASS 2)
30 ARRAY LDA ARRAYTOP;PUT TOP-OF-ARRAY VALUE INTO THE DYNAMIC POINTER (PARRAY)
40 STA PARRAY; IN OTHER WORDS, MAKE PARRAY POINT TO THE HIGHEST WORD IN THE
50 LDA ARRAYTOP+1; LABEL ARRAY
60 STA PARRAY+1
70 JSR DECPAR
80 LDA #$FF; SET UP FOR BMI TEST IF NO MATCH FOUND
90 STA FOUNDFLAG
100 STARTLK SEC; START LOOKING FOR LABEL NAME
110 LDA MEMTOP; CHECK TO SEE IF WE'RE AT THE BOTTOM OF THE ARRAY
120 SBC PARRAY
130 LDA MEMTOP+1
140 SBC PARRAY+1
150 BCS ADONE; IF SO, CHECK IF WE FOUND THE LABEL (OR FOUND IT TWICE)
160 LDX #0; SET LABEL NAME SIZE COUNTER TO ZERO
170 SEC, GO DOWN 2 BYTES IN MEMORY (PAST THE INTEGER VALUE OF A LABEL)
180 LDA PARRAY
190 SBC #2
200 STA PARRAY
210 LDA PARRAY+1
220 SBC #0
230 STA PARRAY+1
240 LDY #0
260 LPAR LDA (PARRAY),Y; LOOK FOR A 7TH BIT SET (START OF LABEL NAME)
270 BMI FOUNDONE; IF YES, WE'VE GOT TO THE START OF A NAME.
280 LDA PARRAY; OTHERWISE GO DOWN 1 BYTE IN ARRAY
290 BNE MDECX
300 DEC PARRAY+1
310 MDECX DEC PARRAY
320 INX; INCREASE LABEL NAME SIZE COUNTER
330 JMP LPAR
350 FOUNDONE LDA PARRAY; WE'VE LOCATED A LABEL NAME IN THE ARRAY
360 STA PT; REMEMBER IT'S STARTING LOCATION
370 LDA PARRAY+l
380 STA PT+1
390 LDA (PARRAY),Y
400 CMP WORK; COMPARE THE 1ST LETTER WITH THE 1ST LETTER OF THE TARGET WORD
410 BEQ LKMORE; LOOK MORE CLOSELY AT THE WORD, IF 1ST LETTER MATCHED
420 JMP STARTOVER; IF IT DIDN'T MATCH, GO DOWN IN THE TABLE & FIND NEXT WORD.
440 LKMORE INX; RAISE LENGTH COUNTER BY 1
450 STX WORK+l; REMEMBER IT
460 LDX #1
470 LDA BUFLAG;THIS MEANS THAT # OR ( COME BEFORE THE NAME IN THE BUFFER
480 BEQ LKM1; IF THEY DON'T WE DON'T NEED TO RAISE Y IN ORDER TO IGNORE THEM
500 JSR DECPAR; LOWER THE INDEX TO COMPENSATE FOR THE INY
520 LKM1 INY
530 LDA BUFFER,Y; CHECK BUFFER-HELD LABEL
540 BEQ FOUNDIT; IF WE'RE AT THE END OF THE WORD (0), THEN WE'VE FOUND A MATCH
550 CMP #48; OR THERE'S A MATCH IF IT'S A CHARACTER LOWER THAN ASCII 0 (,OR+)
560 BCC FOUNDIT
570 ; NOT YET THE END OF THE "BUFFER" HELD LABEL
590 CMP (PARRAY),Y; IF ARRAY WORD STILL AGREES WITH BUFFER WORD, THEN
600 BEQ LKM1; CONTINUE LOOKING AT THESE WORDS
610 ;--------------------------- NO MATCH, SO LOOK AT NEXT WORD DOWN -----
620 STARTOVER LDA PT; PUT PREVIOUS WORD'S START ADDR. INTO POINTER
630 STA PARRAY
640 LDA PT+1
650 STA PARRAY+1
660 JSR DECPAR; LOWER POINTER BY 1 (STARTLK WILL LOWER IT ALSO, BELOW VALUE)
670 JMP STARTLK; TRY ANOTHER WORD IN THE ARRAY
690 ADONE LDA FOUNDFLAG
700 BMI AD1; DIDN'T FIND THE LABEL
710 RTS; ALL IS WELL. RETURN TO EVAL.
720 AD1 LDA PASS
730 BNE ADlX; 2ND PASS-- GO AHEAD AND PRINT ERROR MESSAGE
740 BEQ ADONE1; ON 1ST PASS, MIGHT NOT YET BE DEFINED (RAISE INCSA/2S OR 3S)
750 ADlX JSR ERRING; LABEL NOT IN TABLE. (TREAT IT AS A 2-BYTE ADDRESS)
760 JSR PRNTLINE
770 JSR PRNTSPACE
780 LDA #<NOLAB
790 STA TEMP
800 LDA #>NOLAB
810 STA TEMP+1
820 JSR PRNTMESS; RING BELL AND PRINT NOT FOUND MESSAGE
830 JSR PRNTCR
840 ADONEI PLA
860 LDA OP
870 AND #31
880 CMP #16
890 BEQ AD02; CHECK IF BRANCH INSTRUCT.
900 LDA BYTFLAG
910 BNE AD02; < OR > PSEUDO
920 JMP THREES
930 AD02 JMP TWOS
950 FOUNDIT CPX WORK+I;CHECK LABEL LENGTH AGAINST TARGET WORD LENGTH
960 BEQ FOUNDF; THEY MUST EQUAL TO SIGNIFY A MATCH. (PRINT/PRIN WOULD FAIL)
970 JMP STARTOVER; FAILED MATCH
980 FOUNDF INC FOUNDFLAG; RAISE FLAG TO ZERO (FIRST MATCH)
990 BEQ FOFX; IF HIGHER THAN 0, PRINT DUPLICATION LABEL ERROR MESSAGE
1000 JSR DUPLAB
1010 FOFX LDY WORK+1
1020 LDA BUFLAG; COMPENSATE FOR # AND
1030 BEQ FOF
1050 FOF LDA (PARRAY),Y; PUT TABLE LABEL'S VALUE IN RESULT
1060 STA RESULT
1080 LDA (PARRAY),Y
1090 STA RESULT+1
1100 LDA BYTFLAG
1110 BEQ CMPMO; IS IT > OR < PSEUDOPRINT
1120 CMP #2
1130 BNE AREND
1140 LDA RESULT+1; STORE HIGH BYTE INTO LOW BYTE
1150 STA RESULT
1160 CMPMO LDA PLUSFLAG; DO ADDITION + PSEUDO OP
1170 BEQ AREND
1180 CLC; ADD THE + NUMBER "ADDNUM" TO RESULT
1190 LDA ADDNUM
1200 ADC RESULT
1210 STA RESULT
1220 LDA ADDNUM+1
1230 ADC RESULT+1
1240 STA RESULT+1
1250 AREND LDA PASS; ON 2ND PASS, CHECK FOR DUPS
1260 BNE ARENX
1270 RTS; GO BACK TO EVAL
1280 ARENX JMP STARTOVER; ON PASS 2, LOOK FOR DUPS (SO CONTINUE IN ARRAY)
1300 DECPAR LDA PARRAY; LOWER ARRAY POINTER BY 1
1310 BNE MDEC
1320 DEC PARRAY+1
1330 MDEC DEC PARRAY
1350 ; -----------------
1360 DUPLAB JSR ERRING; RING BELL AND PRINT DUP LABEL MESSAGE
1370 LDA #<MDUPLAB
1380 STA TEMP
1390 LDA #>MDUPLAB
1400 STA TEMP+1
1410 JSR PRNTMESS
1420 JSR PRNTCR
1440 FILE OPEN1
For the Atari version of Array, change line 1440 to:
1440 .FILE D:OPENI.SRC
Return to Table of Contents | Previous Chapter | Next Chapter