The Main Input Routine
It's up to the Indisk subprogram to pull in a logical line of source code and set it up so that Eval can evaluate it. What does the word logical mean when used this way? You'll sometimes hear of a "logical" string or a "logical" line versus a "physical" string or line. The logical thing is what the computer will see and compute. The physical thing might well be longer or shorter.
For example, on the Apple, Atari, and Commodore 64, the screen permits a physical line of only 40 characters. And though each screen line can hold only 40 characters, Commodore BASIC can interpret 80-character lines, Apple can interpret 256-character lines, and the Atari can interpret 120-character lines. The logical line length is 80, 256, or 120 characters, but the physical line is 40. To describe Indisk's routines, we'll need to make a similar distinction.
Two physical lines of LADS source code might be:
100 LDA 15: INY:RTS
110 DEC 15
but there are four logical lines in these two physical lines:
Put another way, the LADS logical line is sometimes smaller than its physical line. The logical item is the piece that a computer-or in this case, LADS-will work with. Whenever you see a colon, you're at the end of a logical line.
In addition to setting up each logical line for examination by Eval, Indisk also performs some other tasks. It sets flags up in response to several pseudo-ops; it transforms single-byte tokenized BASIC keywords into ASCII words (? becomes PRINT); it transforms ASCII hex numbers like $1500 into twobyte integers (the same thing the Valdec subprogram does for ASCII decimal numbers); and it handles the important BYTE pseudo-op. Indisk is a busy place. It's the second longest source file in LADS. Eval interprets logical lines of source code; Indisk prepares them for that interpretation.
Total Buffer Cleaning
Indisk starts by cleaning out an entire group of buffers: LABEL, BUFFER, BUFM, HEXBUF, FILEN, NUBUFF. That's easy because they are all stuck together (see lines 290-340 in the Tables subprogram). The CLEANLAB subroutine in Eval just sticks 0 into the entire string of buffers.
Then 0 is put into the HEXFLAG (is it a $ type number?), BYTFLAG (is it a < or > pseudo-op?), and PLUSFLAG (is it a + pseudo-op?). These three flags will later be set up, if necessary, by Indisk. We want them down, however, at the start of our analysis of each logical line.
At line 110 LADS sees if the previous logical line ended in a colon. LADS tries to be forgiving. It knows that the programmer might accidentally write source code like:
100 LDA 15: LDX 12
leaving some spaces between a colon and the start of the next logical line. Rather than crash trying to find a label called blank-blank-L-D-X, it ignores leading blanks following colons. Elsewhere, LADS ignores blanks preceding semicolons. This gives the user complete freedom to ignore that potential punctuation problem. Logical lines with extra blank spaces will be correctly analyzed.
If a colon ended the previous logical line, we need to skip over the fetch-and-store-line-number routine (130-160) since there is a line number only at the start of a physical line. In BASIC programs, and consequently in LADS source code, the two bytes just preceding the start of the code proper in each physical line are the line number. They need to be remembered by LADS for printouts and also for error reporting.
The Suction Routine
Lines 170-190 are the suction routine for blanks which might precede a colon. We just loop here until something other than the blank character (#32) is encountered. Notice that this loop is also performed at the start of a physical line, but will have no effect since the computer removes any leading spaces when you first type in a BASIC or LADS line.
Line 210 is the start of the main loop which pulls in each character from the disk, one at a time. We skip over this (200) if we've entered at Indisk and therefore are starting a line rather than just looking at the next character within a line.
But let's assume for now that we're trying to get the next character in a line. If it's zero, that means the end of a physical line (230), so we go to the routine which checks to see if we're at the end of the entire program, not just the end of a single line.
If there was no zero, we check for a colon and jump to the routine which handles that (260). Then we check for a semicolon. The next section (290-750) handles semicolons. There are two types of semicolon situations, requiring two different responses.
One type of semicolon defines an entire line as a comment. The semicolon, announcing that a remark follows, appears in this case as the first character in a physical line:
100; THIS ENTIRE LINE IS A REMARK.
This type is relatively simple since there is no source code for Eval to evaluate.
The other type of remark, though, appears at the end of a logical line, and there is something for Eval to assemble on such lines:
100 LDA 75; ONLY PART OF THIS LINE IS A REMARK.
When we first detect a semicolon (270), we store the Y Register in variable A (290). The Y Register is very important in Indisk. It is set to zero at the start of each physical line (60) and will still be zero in line 290 if the semicolon is the first character in a physical line. This is how we can tell which type of comment we're dealing with (at the start of a line or within a line).
If, however, the programmer has not requested a screen printout, there is no point to storing a comment. Comments have no meaning to the assembler; they're just a convenience to the programmer. Line 300 checks to see if PRINTFLAG is set and, if not, skips over the store-the-comment routine.
BABFLAG for Comments
But if the PRINTFLAG was up (contained a 1), we transfer that 1 to force the BABFLAG up as well. BABFLAG tells LADS that there's a comment to be printed after the source and object codes have been printed to screen or printer.
Then that previously stored Y Register is pulled back out, and we see which kind of comment we're dealing with. If Y isn't zero, we've got a within-the-line comment, and we can JSR to the PULLREST subroutine which stores comments in the comment buffer (350). Then we return to Eval to assemble the first part of the line, the source code part (360).
When a semicolon appears at the start of a line, though, we'll just fill LABEL, the main buffer, with the comment and then print out that kind of line right here within Indisk. (Printouts are normally controlled by Eval following the assembly of source code. This type of line, however, contains no source code.)
A little loop (370-440) stuffs the comment line into LABEL. It exits when it finds the end of a physical line (380), and it JSRs when it comes upon a tokenized keyword like PRINT or STOPIT. (STOPIT would appear as three characters in the source code: the token for BASIC's STOP command, and the letters I and T.) Tokenized words have to be stretched out to their ASCII form, or the comment could contain strange nonprinting characters or graphics characters, etc., when printed out. Any character larger than 127 is not a normal alphabetic character. It's going to be a token.
When we finally come upon the end of this physical comment line, we land at PUX1 (450) and proceed to print the line number, the comment, and a carriage return just as we do for any other line. Then we put 0 into the A variable to let MPULL (the return-to-Eval subroutine) know that there is no source code to assemble in this line. It will send us back to two different places in Eval, depending on whether we should or shouldn't try to assemble the line currently held in the LABEL buffer.
Storage to BABUF
The PULLREST routine (520-600) is similar to the PUX routine above it, but it stores a comment into the BABUF buffer. PULLREST cannot use the LABEL buffer because this is one of those lines where the comment comes after some legitimate source code. And Eval assembles all legitimate source code from the LABEL buffer. After Indisk turns the following line over to Eval:
100 LDX 22; HERE IS A COMMENT.
the two buffers hold their respective pieces of this line:
LABEL LDX 22
BABUF HERE IS A COMMENT.
BABFLAG is set up to alert Eval to print a comment after it has assembled and printed out the LDX 22 part of this line (520). Then the semicolon in the Accumulator is saved in the A Register. This is our end-of-line condition. Logical lines can also end with colons and zeros. Different end-of-line conditions require different kinds of exits from Indisk. For example, if we hit a colon, we shouldn't pull in the next two characters and store them as a line number. A colon means we've not yet reached the end of the physical line. Since PULLREST is used as a subroutine in various ways-JSRed to from various places in Indisk-it must save the end-of-line condition.
Then PULLREST pulls the rest of the line into BABUF (560-650) with a little detour to KEYWAD if the seventh bit is set on one of the characters being pulled in. That signals a tokenized keyword like ? for PRINT. KEYWAD is the same routine as KEYWORD (called above when Indisk is pulling in source code characters). The only difference between them is that KEYWORD extends ? to the word PRINT in LABEL, the source code buffer. KEYWAD extends tokens into BABUF, the comment buffer.
PULLRX (660-680) is quite similar to PULLREST. However, PULLRX is a pure suction routine. It pulls in the rest of a comment line, but doesn't store any of the characters. It is called upon when the PRINTFLAG is down and nothing needs to be printed to screen or printer. All PULLRX does is get us past the comment to the next physical line.
MPULL (690-750) is the exit from Indisk back to Eval after a commented line has been handled. Recall that there are two kinds of comments-those which take up an entire physical line and those which take up only the latter part of a line, those which come after some real source code. MPULL distinguishes between them after first checking to see if we're at the end of the entire program (ENDPRO). It loads in the A variable. If A is holding a zero, that would mean that the semicolon was the first character in the physical line, and consequently, the entire line was a comment and can be ignored. There's nothing to assemble. So we PLA PLA to get rid of the RTS address and JMP directly to STARTLINE in Eval to get a new physical line.
Y Is the Pointer
Alternatively, if the semicolon was not at the start of the line, the value in the A variable will be higher than zero. (The Y Register was stored in A when a semicolon was first detected .) Y keeps track of which position we are currently looking at within each physical line. In cases where there is some source code on a line for Eval to assemble, we just RTS (750) back to Eval where the evaluation routine begins.
The end of the main Indisk loop is between lines 760 and 950. This section is an extension of the character-testing sequence found between lines 220 and 270. What's happening is that a single character is being drawn in from the source code (on a disk file or within RAM memory, depending on which version of LADS you are using). Each character is tested for a variety of conditions: pseudo-ops, keyword tokenization, hex numbers, end-of-line (220), colon (240), and semicolon (270). If it was a semicolon, we dealt with it before making any further tests. The semicolon (comments) handler is the large section of code we just discussed (between lines 290 and 750). If the character isn't a semicolon, however, there are several other special cases which we should test for before storing the character into LABEL, the source code buffer.
Is it a > pseudo-op? If so, we go to the routine which handles that (770) called HI. Is it the < pseudo-op? Then go to the LO routine. Is it the plus sign, signaling the + pseudo-op? If not, jump over line 820. The + pseudo-op is handled elsewhere in LADS; all we do for now is set up the PLUSFLAG (820). Is it the * =, the Program Counter changing pseudo-op? If so, go to the subroutine which fixes that (850). Is it one of the pseudo-ops which start with a period, like BYTE or FILE? If so, go to the springboard to the subroutines which deal with these various pseudo-ops (870). Is the character a $, meaning that the source code number which follows the $ should be translated as a hex number? If so, go to the hex number routine springboard (890).
The final test is for tokenized keywords (? for PRINT). Tokens all have a value higher than 127, so their seventh bit will be set. If the character is lower (BCC) than 127, we can finally add the character to the source code line we're building in the LABEL buffer (930). Then we raise the Y Register to point to the next available space in the LABEL buffer, and return to fetch the next available space in the LABEL buffer, and return to fetch the next character of source code from disk or RAM memory (950).
This ends the main loop of the Indisk routine. As you see, there are many tests before a character can be placed into the LABEL buffer. We only want to give Eval source code that it can assemble. We can't give it characters like . or + or $ which it cannot evaluate properly. Those, and other special conditions, are worked out and fixed up by Indisk before LADS turns control back to the Eval subprogram.
The Colon Logical End-Of-Line
One special condition is the colon. It is handled at the very start of Indisk as a new physical line is analyzed (110). Not much needs to be done with colons except to ignore them. But we do need to prevent LADS from trying to locate the next physical line number. Colons signify the end of a logical line, not the end of a physical line. COLFLAG tells Indisk not to look for a line number. COLFLAG is set whenever a colon is detected (260). We jump down to COLON (970) and set the flag. We don't need to LDA #1:STA COLFLAG because we wouldn't be here unless the Accumulator was holding a colon character (it's higher than 0). We can just stuff that character into COLFLAG. As long as a flag isn't holding a 0, it's set. When setting flags, it doesn't matter that the number in the flag is higher than 1. Just so it's not 0.
There are two springboards at 990-1020. Recall that branch instructions like BNE cannot go further than 128 bytes in either direction, so you'll get a BRANCH TOO FAR error message from LADS from time to time when you exceed this limit. In such cases, just BNE SPRINGBOARD; just branch to a line you insert, like 990, which just has a JMP to your true target.
Like the . pseudo-op interpreter subroutine, the hex translator is also too far from the branch which tries to reach it. With a hex number, though, we first put the $ into the LABEL buffer so it will be printed when the source code line is sent to the screen or printer. Then we bounce off to the hex translator subroutine (1020).
KEYWORD (1040-1210) translates one of BASIC's tokens into a proper English word. A BASIC word like PRINT is a word to us programmers, but an action, a command, to the computer. To save space, many versions of BASIC translate the words into a kind of code called "tokens." The token for PRINT might be the number 153, which can fit into a single byte. The word PRINT takes up five bytes.
But BASIC itself must detokenize when it lists a program. It must turn that 153 back into the characters P-R-1-N-T. To do that, it keeps a table of the keywords in ROM. We'll take advantage of that table to do our own detokenization.
The specifics of the example we'll examine here are for Commodore computers. The principle, however, applies to Apple and Atari as well. Only the particular numbers differ. We arrive here at KEYWORD because we picked up a character with a value higher than 127. The first thing we do is subtract 127. That will give us the position of this keyword in the table of keywords. To see how this works, look at how these words are stored in ROM memory:
Notice that BASIC stores words in this table with their last letter shifted, similar to the way LADS stores labels with their first letter shifted. That's how the start of each word can be detected. The code for these words is set up so that END = 128, FOR = 129, NEXT = 130, and so on.
Imagine that we picked up a 129 and came here to the KEYWORD subroutine to get the ASCII form of the word, the readable form. We would subtract: 129 - 127 = 2. Then we would look for the second word in the table. We store the results of our subtraction in the variable KEYNUM (1060) and keep DECing KEYNUM until it's zero and we've thus located the word. We look at the first character in the table of keywords. It will be an e. If it's not a shifted character, we've not yet come to the end of a word, and we keep looking (1120). Otherwise, we go back and DEC KEYNUM. All of this is just a way of counting through the keyword table until we get to the word we're after.
When we find it (1140), we store the ASCII characters from the table into LABEL, our main input buffer. Again, a shifted character in the table shows us that we've reached the end of the word (1160), and we can return to the caller (the routine we JSRed here from) after clearing out the seventh bit.
KEYWORD turns this line (in the source code):
100 START? LDA [IT (two embedded keyword tokens, ? and [) into:
100 STARTPRINT LDA RUNIT (which we can read from screen or printer)
The HI subroutine (1230) handles the > pseudo-op which gets the high byte of a two-byte label as shown in Listing 6-1.
50 SCREENPOINTER = $FD; ZERO PAGE POINTER FOR INDIRECT Y ADDRESSING
100 SCREEN = $0400; DEFINE START OF SCREEN RAM
110 LDA #>SCREEN; LOAD IN HIGH BYTE OF SCREEN ADDRESS
120 STA SCREENPOINTER+l; STORE IT IN HIGH BYTE OF SCREEN POINTER
130 LDA #<SCREEN; LOAD IN LOW BYTE OF SCREEN ADDRESS
140 STA SCREENPOINTER; STORE IT IN LOW BYTE OF SCREEN POINTER
10 *= 800
100 LDA 15
110 JMP CONTINUE; (AT THIS POINT WE'RE AT ADDRESS 805)
120 *= 855 (THIS RESETS THE PC TO 855)
130 CONTINUE INY; (THIS WILL ASSEMBLE AT ADDRESS 855,
140 ; LEAVING A 50-BYTE-LONG BUFFER OR
150 ; STORAGE ZONE FOR VARIABLES.)
10 320 *= 800
100 320 A5 0F LDA 15
110 322 4C 58 03 JMP CONTINUE; (AT THIS POINT WE'RE AT ADDRESS 805)
120 325 *= 855 (THIS RESETS THE PC TO 855)
130 357 C8 CONTINUE INY; (THIS WILL ASSEMBLE AT ADDRESS 855,
140; LEAVING A 50-BYTE-LONG BUFFER OR
150; STORAGE ZONE FOR VARIABLES.)
This sort of thing is fairly common during the initialization phase of an ML program. It prepares for the useful Indirect Y addressing mode (sometimes called Indirect Indexed addressing: LDA (LABEL),Y). The > and < pseudo-ops make it easy to set up the zero page pointers upon which Indirect Y addressing depends.
The adjustments necessary to make these pseudo-ops work are performed in the Equate subprogram. All we do here is set up the BYTFLAG to show which of them was encountered. BYTFLAG is 0 normally, set to 1 for a < low byte request and 2 for a > high byte request. Then we go back to fetch the next character in the source code. The > and < symbols are not stored in the LABEL buffer.
Don't Drive with Your Legs Crossed
The STAR subroutine (1300) deals with the pseudo-op which changes the Program Counter. This pseudo-op has one primary use: It creates a stable place for tables. Some people like to use it to make room for tables within source code (and consequently within the resulting object code too). That seems both unnecessary and dangerous, like driving with your legs crossed. Most of the time it won't do any damage, but when it does cause problems, it causes a crash.
If you like to live dangerously, go ahead and stick a table or a buffer right in the middle of your code. The *= pseudo-op allows coding as shown in Listing 6-2. When assembled, that risky trick will look like the listing shown in Listing 6-3. This example leaves-between $325 and $357-a 50-bytelong zone to be used for data rather than instructions. You must jump over the table. But what's the point? Why not do the sensible thing and put all your tables, register, buffer, etc.-all your nonprogram stuff-in one place? At the end of the entire program. Not only does that ease your programming task by making it simple to understand what you're trying to do, it also allows the *= pseudo-op to make its true contribution to assembling: a stable table.
When you're assembling a long program, you will often go through a two-step process. You'll assemble, then test. The test fails. You change the source code and try it again. This assemble-test rhythm takes place so often that you'll want to make it as easy on yourself as possible. One of your best debugging techniques will involve running your code and then looking in the buffers, registers, variables, and other temporary storage places to see just exactly what is there. That's usually the best clue to what went wrong. If you are trying to load in the word TEXTFILE from disk and your buffer holds EXTFILEO, that tells you exactly what you need to do to fix up the source code.
In other words, you want to be able to check buffers, variables, etc., often. Where are they located in the object code? Obviously, each time you make a slight change to the source code, everything in the object code above the change in memory shifts. All the addresses beyond the changed source code will go up or down depending on whether you added or subtracted something.
This makes for very unstable addresses. You would never know where to PEEK at a particular buffer or variable.
There are two ways to solve this. You could put the data buffers, etc., at the start of your program. That way, they wouldn't shift when you changed the source code beyond them. But that's somewhat clumsy. That means that your program doesn't start with the first byte. The entry to your program is up higher, and you can't just SYS or CALL or USR to the first byte.
An alternative, and likely the best, idea is to put tables at the very end. That way the SYS to the object code start address is also the first byte of the ML program. But how does this solve the shifting tables problem? That's where the *= comes in.
When I first started to write LADS, I decided to start it at $3A00. That left plenty of room below for BASIC-type source files and plenty of room above for "Micromon," an extended debugging monitor program which sits in memory between $5B00 and $7000. (1 do all my programming on the venerable, but serviceable, Commodore PET 8032.) LADS was expected to end up using about 4K of memory, so I forced Tables, the final source file, to detach itself from the rest of the program and to assemble at $5000. The Tables subprogram started off like this:
20 * = $5000
30 MNEMONICS etc.
This kept everything in the Tables unaffected by any changes in the program code below it. The entire source code could be massaged and manipulated without moving the data tables one byte up or down in memory. A detached table is a stable table.
So, during the weeks while LADS was taking shape, I learned the addresses of important buffers like LABEL and important variables and flags. That makes debugging much faster. Sometimes, I could tell what was wrong by simply PEEKing a single flag after a trial run of the source code.
A program the size of LADS, a complex game, or any other large ML program, will require perhaps hundreds of assemblies. It becomes very useful to have learned the special addresses, like buffers, where the results of a trial run of your object code are revealed. And for this reason, these buffer and flag addresses should stay the same from the day you start programming until the day the entire program is composed.
How is the *= pseudo-op handled? Before anything else, we pull in the rest of the source code line by a JSR to STINDISK, the main loop in Indisk. After that, STAR checks to see if anything should be printed out by looking at PASS. On pass 1, we'll skip over the printout (1320). Otherwise, we print the star and the input line held in the LABEL buffer. We won't check to see if a printout is requested by looking at PRINTFLAG or SFLAG (screen printout). * = is such a radical event that it will be displayed on pass 2 whether or not any printouts were requested.
Then we come to the familiar hex or decimal number question. Hex numbers are translated and put into the RESULT variable as they stream in. Indisk does hex. Decimal ASCII isn't automatically put into RESULT. If the argument following *= was hex, we skip over the next few lines (1380). If not, we look for the blank character (in *= 500, the character between the = and the 5). Finding that (1420), we point the TEMP variable to the ASCII decimal number and JSR VALDEC to give the correct value to RESULT. We'll use RESULT to adjust the PC as requested.
Padding the Disk File
If the programmer wants object code stored to disk, we cannot just change the internal LADS program counter. The disk drive won't notice that. We've got to pad the disk program: We've got to physically send spacer bytes to the disk to move its pointer the correct number of bytes forward. Object code is stored only on pass 2.
Thus, two questions are asked here. Does the programmer want object code stored? And is the disk drive a recipient of that object code? If the answer to both questions is "yes," we JSR FILLDISK (1590), a padding routine we'll come to later. If not, the whole issue of disk padding doesn't matter and we can proceed to adjust the PC (SA is the variable name of the LADS Program Counter) by transferring RESULT into it (1600-1630). Then we PLA PLA the RTS off the stack and jump back into Eval to get the next physical line.
ENDPRO is a short but essential routine. After each physical line we need to see if we've reached the end of the source code program. Microsoft BASIC signals the end of a BASIC program with three zeros.
But before checking for those telltale zeros, ENDPRO fills the buffers with zeros to clean them (1680-1710).
Then it pulls in the next two characters. If the second one is a zero, we know it's the end of a source file (not necessarily the end of a series of chained source files; that's flagged by the END pseudo-op). However, if it is the end of a program file, we flip the ENDFLAG up to warn Eval and RTS back to Eval (1790). Even though Indisk has discovered that we're at the same last line in a file, Eval still has that last line to evaluate and assemble. The ENDFLAG won't have any immediate effect when we first return to Eval.
The other possibility is that we won't find the three zeros and that this isn't the last line of a file. If it isn't, we just set the COLFLAG down because at least we're at the end of a physical line. A zero always means that. Then we return to Eval. Indisk just pulls in one line at a time.
HEX is an interesting routine. It is called when Indisk detects the $ character. HEX looks at the ASCII form of a number like $0F and turns it into the equivalent two-byte integer 00 0F in RESULT. It's similar to the subprogram Valdec which translates an ASCII decimal number into an integer.
HEX operates like a little Indisk. It pulls in characters from the source code, storing them in its own special buffer, HEXBUF, until it finds either a zero, a colon, a blank, a semicolon, a comma, or a close parenthesis character. Each of these symbols means that we've reached the end of the hex number. Some of them signal the end of a line, some of them don't. Whichever category they fall into, they go to the appropriate routine, DECI or DECIT.
Busy X and Y
If we're not yet at the end of the hex number, however, the character is stored in HEXBUF (1970) for later translation and also stored in LABEL for printout. Notice that both the X and the Y Registers are kept busy here, indexing their respective buffers. Y cannot do double duty because it is farther into the LABEL buffer than X; the LABEL buffer is holding the entire logical line, HEXBUF is holding only the ASCII number. The two buffers will look like this when the source line HERE LDA $45 is completely stored:
LABEL HERE LDA $45
LABEL will be analyzed and assembled by Eval. It needs to store the entire logical line. HEXBUF will be analyzed only to extract the integer value of the hex number. Storing anything else in HEXBUF would be confusing.
A hex number which is not at the end of a line goes to DECIT (2020) and, the length of the hex number is stored into the variable HEXLEN (2020) so we'll know how many ASCII characters there are to translate into an integer. Then the final character (a comma or whatever) is put into the LABEL buffer. Then the JSR to STARTHEX (2050) translates the ASCII into an integer in RESULT. A JMP (rather than a JSR) to STINDISK pulls in the rest of the logical line and takes us away from this area of the code. The assembler will not return to this area. It will treat the rest of the line as if it were an ordinary line.
By contrast, a hex number which is at the end of a line goes to DECI (2070), and we store the type of end-of-line condition (colon, semicolon, 0) in the variable A. We put the length of the hex number into the variable HEXLEN (2090), so we'll know how many ASCII characters there are to translate into an integer. And we put a 0 delimiter at the end of the information in the LABEL buffer. Then the JSR to STARTHEX (2110) translates the ASCII into an integer in RESULT. We restore the colon or semicolon or whatever (2120) and jump to the routine which provides a graceful exit (2130).
STARTHEX turns a hex number from its ASCII form into a two-byte integer. It does this by rolling the bits to the left, pulling the number into RESULT's two bytes, and adjusting for alphabetic hex digits (A-F) as necessary.
The variable HEXLEN knows how many characters are in the hex number. It will tell us how many times to go through this loop. Before entering the loop, we clean the RESULT variable by storing zeros into it (2140-2160) and set the X Register to zero.
The loop proper is between lines 2180 and 2350, and is largely an ASL/ROL massage. Each bit in a two-byte number is marched to the left. ASL does the low byte, ROL the high byte. ASL moves the seventh bit of RESULT into the carry. ROL puts the carry into the zeroth bit of RESULT+1, the high byte.
As an example of how this ASCII-to-integer machinery works, let's assume that the number $217 is sitting in the HEXBUF. As ASCII, it would be 2F. But recall that the ASCII code simplifies our job somewhat since the number 2 is coded as $32. To turn an ASCII hex digit into a correct integer, we can get rid of the unneeded 3 by using AND #$0F.
What complicates matters, however, is those alphabetic digits in hex numbers: A through F. For them, we'll need to subtract 7 to adjust them to the proper integer value. They, too, will have the high four bits stripped off by AND #$0F.
Let's now follow $2F as it rolls into RESULT. $2F, as two ASCII digits in HEXBUF, is: $32 $46 or, in binary form, 00110010 01000110.
HXLOOP starts off by moving all the zeros in RESULT four places to the left. There are four ASL/ROL pairs. The first time through this loop, just zeros move and there's no effect. Then we load in the leftmost byte from the HEXBUF (2260) and see if it's an alphabetic digit. This time we're loading in the $32 (the ASCII 2), so it isn't alphabetic and we branch (to 2300) for the AND which strips off the four high bits:
00110010 ($32, as ASCII code digit)
AND 00001111 ($0F)
00000010 (now a true integer 2)
The ORA command sets a bit in the result if either of the tested bits is set. That's one way of stuffing a new value into RESULT:
00000000 (RESULT is all zeros at this point)
ORA 00000010(we're stuffing the integer 2 into it)
00000010 (leaving an integer 2 in RESULT)
Next the X index is raised and compared to the length of the ASCII hex number (in our example $2F, HEXLEN will hold a 2). X goes from 0 to 1 at this point and doesn't yet equal HEXLEN, so we branch back up (2350) to the start of the loop and roll the 2 into RESULT, making room for the next ASCII digit:
Carry bit high byte low byte
0 00000000 00000010 (our 2 before first ASL/ROL)
0 00000000 00000100 (after)
0 00000000 00001000 (after the 2nd ASL/ROL)
0 00000000 00010000 (after the 3rd ASL/ROL)
0 00000000 00100000 (after the 4th and final ASL/ ROL)
What's happened here is that we've shoved the 2 from the low four bits into the high four bits of RESULT. This makes 2 (decimal) into 32 (decimal), or $20. Why do that? Why make room for the next digit in this way? Because the 2 in $2F is really a hex $20. It's a digit 2, but not number 2. It's not a number 2 any more than the 5 in 50 is a 5. This ASL/ROL adjusts each digit to reflect its position, and position determines the numeric value of any digit.
Now it's time to pick up the F from HEXBUF (2260), and since it has a decimal value of 70, it is higher than 65, so we adjust it by subtracting 7. That leaves us with 63 ($3F). We strip off the 3 with AND $0F:
00111111 ($3F, the adjusted ASCII code digit)
AND 00001111 ($0F)
00001111 (now a true integer F)
and then incorporate this F with the $20 we've already got in RESULT from the earlier trip through the loop:
00100000 (RESULT is holding a $20)
ORA 00001111 (we stuff the F into it)
00101111 (leaving the integer 2F in RESULT)
Again, X is raised and tested to see if we're finished with our ASCII hex number (2340). This time, we are finished. There's nothing more to roll into RESULT so we set up the HEXFLAG. This alerts all interested parties in LADS that they do not need to evaluate this argument. The value is already determined and has been placed into RESULT, ready to be printed out or POKEd as the need arises. Then we return to whatever routine called on STARTHEX for its services.
The important pseudo-op BYTE is also handled within the Indisk subprogram. Any pseudo-op beginning with . comes here to PSEUDOJ (2410) first. All of these . type pseudo-ops require certain preliminary actions, and the first section of PSEUDOJ accomplishes those things. Then they split up and go to their own specific subroutines. Most of them end up going to the subprogram Pseudo.
PSEUDOJ first tests to see if there is a PC address-type label such as the word OPCODES in:
100 OPCODES BYTE 161 160 32 96.
The Y Register will still hold a zero if the . character is detected at the very start of a logical line of source code. That would mean that there is no PC-type label and we don't need to bother storing it into the label array for later reference. Likewise, if this isn't pass 1, we can also skip storing such a label in the label array.
But if it is pass 1 and there is one of those labels at the start of the line, we need to save the A and Y Registers (2450-2470) and JSR EQUATE to store the PC label (and its address) into LADS' label array. Then we restore the values of A and Y (2490-2510) and store the . character in the main input buffer LABEL.
If It's Not B
The character following the . will tell us which pseudo-op we're dealing with, so CHARIN pulls it in and stores it into the buffer (2550). If it's not a B, we branch to the springboard PSEUD1 which sends us to the Pseudo subprogram for further tests (3010).
Now we know it's a BYTE type, but is it the ASCII alphabetic type or the ASCII numeric type? It is BYTE "ABCDE or .BYTE 25 72 1 6?
There is a flag which distinguishes between alphabetic and numeric BYTES: the BNUMFLAG. It is first reset (2600), and we check both the pass and the SFLAG to decide whether we should print out this line or not. If it's pass 2 and SFLAG is set, we print the line number and the PC address. Then we pull in more of this source code line until we hit a space character. If the character following the space isn't a quote, we know that we're dealing with the numeric type of BYTE, so we branch down to handle that at BNUMWERK (2810).
Otherwise, we take care of the alphabetic type. This type is easy. We can just pull them in and POKE them. There's nothing to figure out or translate. These bytes are held in the source code as ASCII characters and will be POKEd into the object code as ASCII characters. The main use for this pseudo-op is to store messages which will later be printed to the screen or printer.
The active parts of this loop are the CHARIN (2820) and the JSR INCSA (2990) or JSR POKEIT (3050). The decision whether to simply raise the PC with INCSA or actually POKE the object code is based on the test of PASS (2970). The rest of the loop (2830-2960) is similar to other tests for end-of-line conditions found throughout LADS. We look for a 0 (2830), a colon (2850), a semicolon (2880), and a concluding quote (2940). Any of these characters signal the end of our alphabetic message. And each condition exits in a way appropriate to it. Semicolons, for example, require that the comment be stored in BABUF for possible printout. To do this, we JSR PULLREST (2900).
PSLOOP stores each character into LABEL, the main input buffer. It also JSRs to the POKEIT routine (in the Printops subprogram) which both stores the character in any object code on disk or memory and raises the PC by 1. Then it jumps back up to the start of the loop to fetch another alphabetic character (3080).
BNUMWERK is more complicated than BY1, the alphabetic .BYTE pseudo-op we just examined. BNUMWERK must not only check for all of those possible end-of-line conditions; it must also translate the numbers following BYTE from ASCII into one-byte integers before they can be POKEd. It's that same problem we've dealt with before: 253 is stored in the source code as three bytes: $32 $35 $33. We need to turn it into a single value: $FD. (One thing simplifies the numeric type BYTE pseudo-op. The programmer can use only decimal numbers in the source code for this pseudo-op. BYTE $55 $FF is forbidden, although you could certainly add the option if you wish.)
Like a small version of the Eval subprogram, BNUMWERK has to have a flag which tells it when to close down. We set this BFLAG down (3100) and then put the character in the Accumulator into a buffer called NUBUF. In this buffer we'll convert these decimal ASCII numbers into integers. Then we raise X to 1 and enter the main BNUMWERK loop (3130).
The BFLAG is tested, and we shut down operations if it is set (3140). Otherwise, we pull in the next character and go through that familiar series of tests for end-of-line conditions: 0, colon, or semicolon. If it is a regular character, we stick it into the special BUFM buffer (3250) and check to see what pass we're on. On pass 1 we don't do any POKEing or printing out, so we can skip that. But on pass 2, we check to see if we've got a space character, indicating that we've reached the end of a particular number, if not yet the end of an entire line (3360). If the number is completely in the buffer, we raise the PC and go back for the next number (3320).
On the second pass, however, we may have to POKE object code and also provide printouts. This means that we have to both calculate each number for POKEing as well as store each number in ASCII form for printouts. We pull the character from the BUFM buffer and store it in the printout buffer, LABEL, the main input buffer (3340). After that we check again for end-ofnumber or end-of-line conditions (3360-3410) and, not finding one, return for another character (3440) after storing the current character in HEXBUF.
An end-of-line condition lands at BSFLAG (3450), which alerts BNUMWERK that it should exit the loop after the current number in HEXBUR has been analyzed.
A Huge, and Incorrect, Number
WERK2 (3480) performs the analysis of a single number. It points the TEMP variable to NUBUF where the number is stored and JSRs to VALDEC, leaving the value of the number in RESULT. Then the value is POKEd to the disk or RAM object code (and the PC is raised by 1) (3550).
So that nothing will be left over to confuse VALDEC during its analysis of the next number, NUBUF is now wiped clean with zeros. VALDEC expects to find 0 at the end of an ASCII number that it's turning into an integer. If that 0 isn't there, VALDEC will keep on looking for it, creating a huge, and incorrect, answer.
Then we return to the main loop and look for another character, the start of another number (3620).
There are so many options in LADS that graceful exits from routines like BNUMWERK are rather difficult. We cannot just simply RTS somewhere. We've got to take into account several sometimes conflicting conditions.
LADS can get its source code from two places: disk or RAM memory. The source code can be entirely within a single program file or spread across a chain of linked files. LADS can assemble hex or decimal numbers from source code (except within the BYTE pseudo-op). The assembler can send its object code to four places: disk, screen, RAM memory, or printer. All or any of these targets can be operative at any given time. And output can be turned on or off at will. No wonder there have to be different exits and some testing before we can leave a pseudo-op. We've got to figure out what's expected, where the object code is going. Finally, the fact that logical lines of source code can end in several ways adds one additional complication to the exit.
BBEND is the start of exit testing for BNUMWERK. On pass 1 we have to raise the PC one final number (3650). If the line ends with a colon, we cannot go to ENDPRO and look for a new line number, since colons end logical, not physical, lines of source code (3680). In either case, we set the COLFLAG up or down, depending on whether or not we've got a colon-type ending to this logical line (3700). We then raise the LOCFLAG to tell Eval to print a PC-type address label and PLA PLA, pulling the RTS off the stack in preparation for a JMP back to Eval. If it's pass 1 or if the printer printout flags are down, we don't need to print anything, and we JMP into Eval at STARTLINE to fetch a new line of source code (3790).
Alternatively, if it's pass 2 or if the PRINTFLAG is up, we go back into Eval at PRMMFIN where comments following semicolons are printed (3780).
FILLDISK (3810) takes care of a problem created by using the *= pseudo-op with disk object code files. Recall that if you wrote source code like:
100 START INY
110 * = 950; leave room here
120 INX; continue on
LADS would normally store the INY and follow it immediately on a disk file with INX. The PC variable (SA) within LADS would have changed. The INX object code being POKEd to RAM would be stored correctly at address 950. But the INX would go to disk at address 901. The disk is receiving its object code bytes sequentially and doesn't hear about any PC changes within the computer during assembly.
FILLDISK subtracts the old PC value from the new adjusted PC value and sends that number of filler bytes to a disk object file. In the example above, 900 would be subtracted from 950, and 50 bytes would be sent as spacers to the disk. This creates a space between INY and INX, a physical space, which will cause the object file to load into the computer with the correct, expected addresses for each opcode.
A secret is revealed here. There are two full passes, but LADS starts to try for a third pass. It is quickly shut down because during this pass the ENDFLAG is up and STARTLINE will detect it. Nevertheless, we cannot store more bytes during this brief condition. Bytes must be stored only on pass 2, not on pass 1 or that temporary attempt at a pass 3 (3840).
Starting the Countdown
If FILLDISK is called upon to act, however, it acts. The disk object file (file #2) is opened (3860), and the old PC is subtracted from the new one (3880-3940). The Accumulator is loaded with a 0 and we start the countdown; the result of our subtraction, in the variable WORK, is decremented for each 0 sent to the disk object file (3960-4000). If WORK hasn't counted down to zero, we continue with this loop (4010 and 4030). Finally, we restore the normal I/O and then return to the caller.
The final subroutine on Indisk is functionally identical to KEYWORD. It turns a token into an ASCII word (turns ? to PRINT), but it sends its results to the BABUF buffer which stores all comments. KEYWORD sends its results to the main buffer LABEL for source code lines. To follow the logic of this subroutine, see the discussion of KEYWORD earlier in this chapter (line 1040 on).
Now we can turn our attention from LADS input to LADS output. The bulk of the next chapter explores the four destinations of assembled code: screen, printer, disk, or memory.
Program 6-1. Indisk
10 ; "INDISK" MAIN GET-INPUT-FROM-DISK ROUTINE
20 ;SETUP/EXPECTS DISK TO POINT TO 1ST CHAR IN A NEW LINE (OR BEYOND COLON)
30 ;RESULTS/EITHER FLAGS END OF PROG. OR FILLS LABEL+ WITH LINE OF CODE
50 INDISK JSR CLEANLAB; FILL LABEL WITH ZEROS (ROUTINE IN EVAL)
60 LDY #0
70 STY HEXFLAG; PUT HEXFLAG DOWN
80 STY BABFLAG; PUT COMMENTS FLAG DOWN
90 STY BYTFLAG; PUT FLAG SHOWING < OR > DOWN
100 STY PLUSFLAG; PUT ARITHMETIC PSEUDO OP (+) FLAG DOWN
110 LDA COLFLAG; IF THERE WAS A COLON JUST PRIOR TO THIS, REMOVE ANY BLANKS
120 BNE NOBLANKS; (THIS TAKES CARE OF: INY: LDA 15: LDX 17 TYPE ERRORS)
130 JSR CHARIN; OTHERWISE, PULL IN THE 1ST CHARACTER (FROM DISK OR RAM)
140 STA LINEN; STORE LOW BYTE OF LINE NUMBER
150 JSR CHARIN
160 STA LINEN+1; STORE HIGH BYTE OF LINE NUMBER
170 NOBLANKS JSR CHARIN; ROUTINE TO ELIMINATE BLANKS FOLLOWING A COLON
175 BNE COOLOOK
176 JSR ENDPRO; THIS HANDLES COLONS PLACED ACCIDENTALLY AT END OF LINE
177 PLA:PLA:JMP STARTLINE
180 COOLOOK CMP #32; (OR FOLLOWING A LINE NUMBER)
190 BEQ NOBLANKS;--------------------------
200 JMP MOI1; SKIP TO CHECK FOR COLON (IT'S EQUIVALENT TO AN END OF LINE 0)
210 STINDISK JSR CHARIN; ENTRY POINT WITHIN LINE (NOT AT START OF LINE)
220 MOINDI BNE MOI1; IF NOT ZERO, LOOK FOR COLON
230 JMP ENDPRO; FOUND A 0 END OF LINE. CHECK FOR END OF PROGRAM (3 ZEROS)
240 MOI1 CMP #58; IS IT A COLON
250 BNE XMO1; IF NOT, CHECK FOR SEMICOLON
260 JMP COLON; FOUND A COLON
270 XMO1 CMP #59; IS IT A SEMICOLON
280 BNE COMOA; IF NOT CONTINUE ON
290 STY A; FOUND A SEMICOLON (REM)
300 LDA PRINTFLAG; IF PRINTOUT NOT REQUESTED, THEN DON'T STORE THE REMARKS
310 BEQ PULLRX
320 STA BABFLAG; SET UP PRINT COMMENTS FLAG (A MUST BE > 0 AT THIS POINT)
330 LDA A; OTHERWISE, CHECK Y (SAVED ABOVE). IF ZERO, IS A SEMICOLON AT
340 BEQ PUX; START OF THE LINE (NO LABELS OR MNEMONICS, JUST A BIG COMMENT)
350 JSR PULLREST; OTHERWISE SAVE COMMENTS FOLLOWING THE SEMICOLON
360 JMP MPULL; AND THEN RETURN TO EVAL ---------------------------
370 PUX JSR CHARIN; PUT NON-COMMENT DATA INTO LABEL BUFFER
380 BEQ PUX1; END OF LINE, SO EXIT
390 CMP #127; 7TH BIT NOT SET (SO IT'S NOT A KEYWORD IN BASIC)
400 BCC PUX2
410 JSR KEYWORD; IT IS A KEYWORD, SO EXTEND IT OUT AS AN ASCII WORD
420 PUX2 STA LABEL,Y; PUT THE CHAR. INTO THE MAIN BUFFER
440 JMP PUX; RETURN TO LOOP FOR MORE CHARACTERS---------------------
450 PUX1 JSR PRNTLINE; PRINT THE LINE NUMBER
460 JSR PRNTSPACE; PRINT A SPACE
470 JSR PRNTINPUT; PRINT THE CHARACTERS IN THE LABEL BUFFER (MAIN BUFFER)
480 JSR PRNTCR; PRINT A CARRIAGE RETURN
490 LDA #0; SET A VARIABLE TO ZERO TO SIGNIFY NOTHING FOR EVAL TO EVALUATE
500 STA A
510 JMP MPULL; GO TO EXIT ROUTINE--------------------------------
520 PULLREST STA BABFLAG; PUT REMARKS INTO BABUF (BUFFER FOR COMMENTS)
530 ; THIS ROUTINE REMOVES (AND SAVES) COMMENTS
540 STA A; SET A VARIABLE TO SIGNIFY NOTHING FOR EVAL TO EVALUATE
550 LDY #0; SET OFFSET TO BABUF BUFFER FOR FILLING WITH COMMENTS
560 PAX1 JSR CHARIN; GET CHARACTER
570 BNE PAX; IF NOT ZERO, CONTINUE
580 STA BABUF,Y; OTHERWISE, WE'RE AT THE END OF THE COMMENT
590 LDY A
600 RTS; Y MUST HOLD OFFSET FOR ZERO FILL (ENDPRO) -----------------
610 PAX BPL PAXA; NOT A KEYWORD (7TH BIT NOT SET)
620 JSR KEYWAD; OTHERWISE, EXTEND KEYWORD INTO AN ASCII STRING
630 PAXA STA BABUF,Y; STORE CHAR. IN REMARK BUFFER
650 JMP PAX1; RETURN TO LOOP TO GET ANOTHER CHARACTER--------------
660 PULLRX JSR CHARIN; JUST PULL IN REMARK CHARACTERS, IGNORING THEM
670 BEQ MPULL; LOOKING FOR THE END OF LINE ZERO
680 JMP PULLRX;--------------------------
690 MPULL JSR ENDPRO; CHECK FOR END OF PROGRAM AND THEN
700 LDA A; SEE IF Y = 0. IF SO, THE SEMICOLON WAS AT THE START OF A LINE
710 BNE MPULL1
720 PLA; Y = 0 SO JUMP BACK TO EVAL TO PREPARE TO GET NEXT LINE
740 JMP STARTLINE; SEMI @ START SO RETURN TO EVAL TO GET NEXT LINE---------
750 MPULL1 RTS; SEMICOLON, BUT NOT AT START OF LINE (RETURN TO CALLER)
760 COMOA CMP #177;------------------ CHECK FOR OTHER ODD CHARACTERS
770 BEQ HI; FOUND >
780 CMP #179
790 BEQ LO; FOUND <
800 CMP #170
810 BNE COMO
820 INC PLUSFLAG; FOUND +
830 COMO CMP #172
840 BNE COM01
850 JMP STAR; FOUND
860 COM01 CMP #46
870 BEQ PSEUDOO; FOUND PSEUDO-OP
880 CMP #36
890 BEQ HEXX; FOUND HEX NUMBER
900 CMP #127; NOT A KEYWORD (7TH BIT NOT UP)
910 BCC ADDLAB
920 JSR KEYWORD; FOUND KEYWORD, SO EXTEND IT INTO AN ASCII STRING
930 ADDLAB STA LABEL,Y; PUT THE CHARACTER INTO THE MAIN BUFFER AND
940 INY; RAISE THE POINTER AND
950 JMP STINDISK; RETURN TO GET ANOTHER CHARACTER (BUT NOT A LINE NUMBER)
970 COLON STA COLFLAG; SIGNIFY COLON BY SETTING COLFLAG
990 PSEUDOO JMP PSEUDOJ; SPRINGBOARD TO PSEUDO-OP HANDLING ROUTINES
1000 HEXX STA LABEL,Y; SPRINGBOARD TO HEX NUMBER TRANSLATOR
1020 JMP HEX
1030 ;-------- TRANSLATE. A SINGLE-BYTE KEYWORD TOKEN INTO ASCII STRING
1040 KEYWORD SEC; FIND NUMBER OF KEYWORD (IS IT 1ST, 5TH, OR WHAT)
1050 SBC #$7F
1060 STA KEYNUM; STORE NUMBER (POSITION) IN BASIC'S KEYWORD TABLE
1070 LDX #255
1080 SKEY DEC KEYNUM; REDUCE NUMBER BY 1 (WHEN ZERO, WE'VE FOUND IT IN TABLE)
1090 BEQ FKEY; AND WE EXIT THIS SEARCH ROUTINE AND STORE THE ASCII WORD
1100 KSX INX; BRING X UP TO ZERO AT START OF LOOP
1110 LDA KEYWDS,X; LOOK AT CHAR. IN BASIC'S TABLE.
1120 BPL KSX;DID NOT FIND A SHIFTED BYTE
1130 BMI SKEY; DID FIND START-OF-KEYWORD SHIFTED CHARACTER -------------
1140 FKEY INX; STORE THE KEYWORD INTO LADS' MAIN BUFFER (LABEL)
1150 LDA KEYWDS,X
1160 BMI KSET; A SHIFTED CHAR. INDICATES END OF KEYWORD, START OF NEXT KEYWORD
1170 STA LABEL,Y; PUT CHAR. INTO LADS' BUFFER
1190 JMP FKEY; LOOP AGAIN FOR NEXT CHAR.---------------------
1200 KSET AND #$7F
1210 RTS; CLEAR OUT BIT 7 AND RETURN TO CALLING ROUTINE
1220 ;------------------ HANDLE > AND < PSEUDO-OPS
1230 HI LDA #2; THE BYTFLAG HAS 3 POSSIBLE STATES:
1240 STA BYTFLAG; 0 = LINE DOESN'T CONTAIN A > OR < PSEUDO
1250 JMP STINDISK; 1 = < (LOW BYTE) TYPE
1260 LO LDA #1; 2 = > (HIGH BYTE) TYPE
1270 STA BYTFLAG; (ACTION IS TAKEN ON THIS PSEUDO-OP WITHIN THE
1280 JMP STINDISK; EQUATE SUBPROGRAM). 0 WE FETCH THE NEXT CHAR.
1290 ;------------------ HANDLE THE *= PSEUDO-OP (CHANGE THE PC)
1300 STAR JSR STINDISK
1310 ;LDA PASS; ON PASS 1, DON'T PRINT OUT DATA TO SCREEN
1320 ;BEQ STARN
1325 LDA #$18:JSR PRINT
1330 LDA #42; PRINT
1340 JSR PRINT
1350 JSR PRNTINPUT; PRINT STRING IN LABEL BUFFER
1360 JSR PRNTCR; PRINT CARRIAGE RETURN
1370 STARN LDA HEXFLAG; IF HEX, THE ARGUMENT HAS ALREADY BEEN FIGURED
1380 BNE STARR; SO JUMP OVER THIS NEXT PART
1390 LDY #0
1400 STAF LDA LABEL,Y
1410 CMP #32
1420 BEQ STAF1
1440 JMP STAF; FIND NUMBER (BY LOOKING FOR THE BLANK: *= 15)
1450 STAF1 INY
1460 STY TEMP; POINT TO ASCII NUMBER
1470 LDA #<LABEL
1490 ADC TEMP
1500 STA TEMP
1510 LDA #>LABEL
1520 ADC #0
1530 STA TEMP+1
1540 JSR VALDEC; TRANSLATE ASCII NUMBER INTO INTEGER (IN RESULT)
1550 STARR LDA PASS; ON PASS 1, LEAVE DISK OBJECT FILE ALONE.
1560 BEQ STARRX
1570 LDA DISKFLAG; ON PASS 2, WE'VE GOT TO STUFF THE DISK OBJECT FILE
1580 BEQ STARRX; IF THE DISKFLAG IS UP (WE ARE CREATING AN OBJECT CODE FILE)
1590 JSR FILLDISK; FILLDISK DOES THIS FOR US.
1600 STARRX LDA RESULT; PUT THE ARGUMENT OF *= INTO THE PC (SA)
1610 STA SA
1620 LDA RESULT+1
1630 STA SA+1
1640 PLA; PULL OFF THE RTS AND
1660 JMP STARTLINE; RETURN TO EVAL FOR THE NEXT LINE OF CODE
1670 ;------------------ IS THIS THE END OF THE ENTIRE SOURCE CODE
1680 ENDPRO STA LABEL,Y; PUT THE ZERO (THAT SENT US HERE) INTO THE MAIN BUFFER
1700 CPY #80
1710 BNE ENDPRO; FILL REST OF BUFFER WITH 00S
1720 STA LABEL,Y
1730 JSR CHARIN; PULL IN THE NEXT 2 BYTES. IF THEY ARE BOTH ZEROS, THEN
1740 JSR CHARIN; WE HAVE, IN FACT, FOUND THE END OF OUR SOURCE CODE FILE
1750 BEQ INEND; AND WE BEQ TO INEND
1760 LDA #0; OTHERWISE WE PUT THE COLFLAG (COLON) DOWN, BECAUSE THIS IS
1770 STA COLFLAG; AN END OF LINE CONDITION, NOT A COLON
1780 RTS; AND RETURN TO CALLER
1790 INEND LDA #1;-------- SET END OF SOURCE CODE FILE FLAG TO UP CONDITION
1800 STA ENDFLAG
1810 RTS; AND RETURN TO CALLER
1820 ;------------------------ CHANGE A HEX NUMBER TO A 2-BYTE INTEGER
1830 ; PULL IN NEXT FEW BYTES, TURNING THEM INTO AN INTEGER IN RESULT
1840 HEX LDX #0; PUTS INTEGER EQUIVALENT OF INCOMING HEX INTO RESULT
1850 H1 JSR CHARIN
1860 BEQ DECI; END OF LINE (SO STOP LOOKING)
1870 CMP #58
1880 BEQ DECI; COLON (SO STOP LOOKING)
1890 CMP #32
1900 BEQ H1; BLANK CHARACTER SO KEEP LOOKING FOR END OF LINE
1910 CMP #59
1920 BEQ DECI; SEMICOLON (SO STOP LOOKING)
1930 CMP #44
1940 BEQ DECIT; COMMA (SO STOP LOOKING, BUT GO TO A DIFFERENT PLACE)
1950 CMP #41; (THIS "DIFFERENT PLACE" HANDLES A NOT-END-OF-LINE CONDITION).
1960 BEQ DECIT; CLOSE PARENTHESIS ) (SO STOP LOOKING)
1970 STA HEXBUF,X; OTHERWISE, PUT THE ASCII-STYLE-HEX CHAR. IN BUFFER AND
1980 INX; RAISE THE INDEX AND
1990 STA LABEL,Y; ALSO STORE IT INTO MAIN BUFFER FOR PRINTOUT AND
2000 INY; RAISE THIS INDEX TOO
2010 JMP H1; THEN KEEP ON PUTTING HEX NUMBER INTO HEXBUFFER-----------
2020 DECIT STX HEXLEN; SAVE LENGTH OF ASCII-HEX NUMBER
2030 STA LABEL,Y; FINISH STORING CHARS. INTO MAIN BUFFER (, OR ) IN THIS CASE)
2050 JSR STARTHEX; TRANSLATE ASCII-HEX NUMBER INTO INTEGER IN RESULT VARIABLE
2060 JMP STINDISK; RETURN TO PULL IN REST OF THE LINE;-----------
2070 DECI STA A; SAVE THE END OF LINE, COLON, OR SEMICOLON CHAR. FOR LATER
2080 LDA #0
2090 STX HEXLEN; SAVE LENGTH OF ASCII-HEX NUMBER
2100 STA LABEL,Y; FINISH STORING CHARS. INTO MAIN BUFFER (0 IN THIS CASE)
2110 JSR STARTHEX; TRANSLATE ASCII-HEX NUMBER INTO INTEGER IN RESULT VARIABLE
2120 LDA A; RETRIEVE 0 OR COLON OR SEMICOLON AND GO BACK UP TO MOINDI WHICH
2130 JMP MOINDI;----------------- BEHAVES ACCORDING TO WHICH SYMBOL A HOLDS.
2140 STARTHEX LDA #0;----------------- HEX-ASCII TO INTEGER TRANSLATOR------
2150 STA RESULT; SET RESULT TO ZERO
2160 STA RESULT+1
2170 TAX; SET X TO ZERO
2180 HXLOOP ASL RESULT; SHIFT AND ROLL (MOVES 2-BYTE BITS TO THE LEFT) -----
2190 ROL RESULT+1; DOING THIS 8 TIMES HAS THE EFFECT OF BRINGING IN
2200 ASL RESULT; THE ASCII NUMBER, 1 BYTE AT A TIME, AND TRANSFORMING IT
2210 ROL RESULT+1; INTO A 2-BYTE INTEGER WITHIN THIS 2-BYTE VARIABLE WE'RE
2220 ASL RESULT; CALLING "RESULT."
2230 ROL RESULT+1
2240 ASL RESULT
2250 ROL RESULT+1
2260 LDA HEXBUF,X; GET A BYTE FROM THE ASCII-HEX NUMBER
2270 CMP #65; IF IT'S LOWER THAN 65, IT'S NOT AN ALPHABETIC (A-F) HEX NUMBER
2280 BCC HXMORE; SO DON'T SUBTRACT 7 FROM IT
2290 SBC #7; BUT IF IT'S > 65, THEN -7. = 65. 65-7 = 58.
2300 HXMORE AND #15; WHEN YOU 58 AND 15, YOU GET 10 (THE VALUE OF A)
2310 ORA RESULT; #15 (00001111) AND #58 (00111010) = 00001010 (TEN)
2320 STA RESULT; PUT THE BYTE INTO RESULT
2330 INX; RAISE THE INDEX
2340 CPX HEXLEN; ARE WE AT THE END OF OUR ASCII-HEX NUMBER
2350 BNE HXLOOP; IF NOT, CONTINUE
2360 INC HEXFLAG; IF SO, RAISE HEXFLAG (TO SHOW RESULT HAS THE ANSWER)
2370 LDA #1; AND RETURN TO CALLER
2400 ; HANDLE PSEUDOS. (.BYTE TYPES)
2410 PSEUDOJ CPY #0; IF Y = 0 THEN IT'S NOT A PC LABEL LIKE (LABEL BYTE 0 0)
2420 BEQ PSE2
2430 LDX PASS; OTHERWISE, ON 1ST PASS, STORE LABEL NAME AND PC ADDR. IN ARRAY
2440 BNE PSE2
2450 PHA; SAVE A AND Y REGISTERS
2480 JSR EQUATE; NAME AND PC ADDR. STORED IN ARRAY
2490 PLA; PULL OUT A AND Y REGISTERS (RESTORE THEM)
2520 PSE2 STA LABEL,Y; STORE . CHAR.
2540 JSR CHARIN; GET CHAR. FOLLOWING THE PERIOD (.)
2550 STA LABEL,Y
2570 CMP #66; IS IT "B" FOR BYTE
2580 BNE PSEUD1; WASN'T BYTE
2590 LDA #0; RESET FLAG WHICH WILL DISTINGUISH BETWEEN BYTE 0 AND BYTE "A
2600 STA BNUMFLAG; " TYPE, OR 00 08 15 172 TYPE (THE TWO BYTE TYPES)
2610 LDA PASS; PRINT NOTHING TO SCREEN ON PASS 1
2620 BEQ CLB
2630 STY Y; SAVE Y REGISTER (OUR INDEX)
2640 ; NOW WE REPLICATE THE ACTIONS OF INLINE (IN EVAL)
2650 LDA SFLAG; SHOULD WE PRINT TO SCREEN
2660 BEQ CLB; NO
2670 JSR PRNTLINE; YES, PRINT LINE NUMBER
2680 JSR PRNTSPACE; PRINT SPACE
2690 JSR PRNTSA; PRINT PC ADDRESS
2700 JSR PRNTSPACE; PRINT SPACE
2710 LDY Y; RECOVER Y INDEX
2720 CLB JSR CHARIN; PULL IN CHARACTER FROM DISK/RAM
2730 STA LABEL,Y; STORE IN MAIN BUFFER
2750 CMP #32; IS IT A SPACE
2760 BNE CLB; IF NOT, CONTINUE PULLING IN MORE CHARACTERS------------
2770 JSR CHARIN; (WE'RE LOOKING FOR THE 1ST SPACE AFTER BYTE)
2780 STA LABEL,Y; STORE FOR PRINTING
2800 CMP #34; IS THE CHARACTER A QUOTE IF SO, IT'S A BYTE "ABCD TYPE
2810 BNE BNUMWERK; OTHERWISE IT'S NOT THE " TYPE
2820 BY1 JSR CHARIN;---------- HANDLE ASCII STRING BYTE TYPES
2830 BNE BY2
2840 JMP BENDPRO; FOUND A 0 END OF LINE (OR PROGRAM)
2850 BY2 CMP #58; FOUND A COLON "END OF LINE"
2860 BNE BY2X
2870 JMP BEN1; FOUND A COLON
2880 BY2X CMP #59; FOUND A SEMICOLON "END OF LINE"
2890 BNE BY3
2900 JSR PULLREST; STORE COMMENTS IN COMMENT BUFFER (BABUF)
2910 LDX PRINTFLAG; IF NO PRINTOUT REQUESTED, THEN
2920 STX BABFLAG; DON'T PRINT COMMENTS
2930 JMP BENDPRO; A SEMICOLON SO END THIS ROUTINE IN THAT WAY.
2940 BY3 CMP #34; HAVE WE FOUND A CONCLUDING QUOTE (")
2950 BNE BY3X
2960 JMP BY1; FOUND A " SO IGNORE IT
2970 BY3X LDX PASS; ON PASS 1, JUST RAISE PC COUNTER (INCSA); DON'T POKE IT.
2980 BNE PSLOOP
2990 JSR INCSA
3000 JMP BY1;-----------------
3010 PSEUD1 JMP PSEUDO; SOME OTHER PSEUDO TYPE, NOT BYTE (A SPRINGBOARD)
3020 PSLOOP STA LABEL,Y; STORE A CHARACTER IN MAIN BUFFER;------------
3040 STY Y; SAVE Y INDEX
3050 JSR POKEIT; PASS 2, SO POKE IT INTO MEMORY (THE ASCII CHARACTER)
3060 LDY Y; RESTORE Y
3070 INY; RAISE INDEX AND
3080 JMP BY1; GET NEXT CHARACTER
3090 BNUMWERK LDX #0;-------- HANDLE BYTE 1 2 3 (NUMERIC TYPE)
3100 STX BFLAG; PUT DOWN BFLAG (END OF LINE SIGNAL)
3110 STA NUBUF,X; WE'RE BORROWING THE NUBUF FOR THIS ROUTINE.
3130 WERK1 LDA BFLAG; IF BFLAG IS UP, WE'RE DONE.
3140 BNE BBEND; SO GO TO END ROUTINE
3150 WK0 JSR CHARIN; OTHERWISE, GET A CHARACTER FROM DISK/RAM
3160 BEQ BSFLAG; IF ZERO (END OF LINE) SET BFLAG UP.
3170 CMP #58; LIKEWISE IF COLON
3180 BEQ BSFLAG
3190 CMP #59; SEMICOLON REQUIRES THAT WE FIRST FILL THE COMMENT BUFFER
3200 BNE WK1; BEFORE SETTING THE BFLAG (IN THE BSFLAG ROUTINE)
3210 JSR PULLREST; HERE'S WHERE THE COMMENT BUFFER IS FILLED
3220 LDX PRINTFLAG; IF NO PRINTOUT REQUESTED, THEN
3230 STX BABFLAG; DON'T PRINT COMMENTS
3240 JMP BSFLAG; FOUND SEMICOLON
3250 WK1 STA BUFM; PUT CHAR. INTO "BUFM" BUFFER
3260 LDA PASS; ON PASS 1, RAISE THE PC ONLY (INCSA), NO POKES
3270 BNE WERK5
3280 LDA BUFM
3290 CMP #32; IS IT A SPACE
3300 BNE WERK1; IF NOT, RETURN FOR MORE OF THE NUMBER (0 VS 555)
3310 JSR INCSA; RAISE PC COUNTER BY 1
3320 JMP WERK1; GET NEXT NUMBER
3330 WERK5 LDA BUFM; PUT CHAR. INTO PRINTOUT MAIN BUFFER
3340 STA LABEL,Y
3360 CMP #32; IS IT A SPACE
3370 BEQ WERK2
3380 CMP #0; IS IT END OF LINE
3390 BEQ WERK2
3400 CMP #58; IS IT COLON
3410 BEQ WERK2
3420 STA NUBUF,X; OTHERWISE, STORE IT
3440 JMP WERK1; AND RETURN FOR MORE OF THE NUMBER----------------
3450 BSFLAG INC BFLAG; RAISE UP THE END OF LINE FLAG
3460 STA BUFM+l; SAVE COLON, SEMICOLON, OR WHATEVER FOR LATER USE
3470 JMP WK1; RETURN FOR MORE (BUT THIS TIME IT WILL END LINE);---------
3480 WERK2 LDA #<NUBUF; POINT TO THE ASCII NUMBER STORED IN BABUF
3490 STA TEMP
3500 LDA #>NUBUF
3510 STA TEMP+1
3520 STY Y
3530 JSR VALDEC; TURN THE ASCII INTO AN INTEGER IN RESULT
3540 LDX RESULT
3550 JSR POKEIT; POKE THE RESULT INTO MEMORY (OR DISK OBJECT FILE)
3560 LDY Y; ERASE THE NUMBER IN HEXBUF
3570 LDA #0
3580 LDX #5
3590 CLEX STA NUBUF,X
3610 BNE CLEX
3620 JMP WERK1; AND THEN RETURN TO FETCH THE NEXT NUMBER;--------------
3630 BBEND LDA PASS; END BYTE LINE. ON PASS 1, RAISE PC (POKEIT RAISES IT
3640 BNE BBEND1; ON PASS 2).
3650 JSR INCSA
3660 BBEND1 LDA BUFM+1; IF END OF LINE SIGNAL WAS A COLON, THEN
3670 CMP #58
3680 BEQ BEN1; DON'T LOOK FOR LINE NUMBER OR END OF SOURCE CODE FILE (ENDPRO)
3690 BENDPRO JSR ENDPRO
3700 BEN1 STA COLFLAG; SET IT (COLON) OR NOT (ENDPRO RETURNS WITH 0 IN A)
3710 INC LOCFLAG; RAISE PRINT-A-PC-LABEL FLAG
3720 PLA; PULL RTS FROM STACK
3740 LDA PASS; ON PASS 1, DON'T PRINT ANY COMMENTS
3750 BEQ NOPR
3760 LDA SFLAG; IF SCREENFLAG IS DOWN, DON'T PRINT ANY COMMENTS
3770 BEQ NOPR
3780 JMP PRMMFIN; BACK TO EVAL (WHERE COMMENTS ARE PRINTED)
3790 NOPR JMP STARTLINE; BACK TO EVAL (BYPASSING PRINTOUT)
3800 ;----------------------- FOR CHANGE OF PC
3810 FILLDISK LDA PASS; A CHANGE OF PC REQUIRES FILLING A DISK OBJECT FILE
3820 CMP #2; WITH THE REQUISITE NUMBER OF BYTES TO MAKE UP FOR
3830 BNE FILLX; THE ADVANCING OF THE PROGRAM COUNTER (PC)
3840 RTS; NOT AT START OF 3RD PASS (3RD PASS IS JUST BEFORE SHUT DOWN)
3850 FILLX JSR CLRCHN
3860 LDX #2
3870 JSR CHKOUT; PUT SPACERS IN DISKFILE FOR *_
3880 SEC; FIND OUT HOW MANY SPACERS TO SEND TO DISK BY SUBTRACTING:RESULT-SA
3890 LDA RESULT
3900 SBC SA
3910 STA WORK; ANSWER HELD IN "WORK" VARIABLE
3920 LDA RESULT+1
3930 SBC SA+1
3940 STA WORK+1
3950 PUTSPCR LDA #0
3960 JSR PRINT; PRINT SPACER TO DISK
3970 LDA WORK; LOWER WORK BY 1
3980 BNE DECWORKX
3990 DEC WORK+1
4000 DECWORKX DEC WORK
4010 BNE PUTSPCR
4020 LDA WORK+1
4030 BNE PUTSPCR; PUT MORE SPACERS IN UNTIL "WORK" IS DECREMENTED TO ZERO.
4040 RESFILL JSR CLRCHN
4050 LDX #1; RESTORE NORMAL I/O
4060 JSR CHKIN
4090 KEYWAD SEC; SEE KEYWORD ABOVE (SAME KEWORD TO ASCII STRING ROUTINE)
4100 SBC #$7F; THIS IS A VERSION OF KEYWORD, BUT FOR COMMENTS(PUTS IT IN BABUF
4110 STA KEYNUM; INSTEAD OF LABEL BUFFER).
4120 LDX #255
4130 SKEX DEC KEYNUM
4140 BEQ FKEX
4150 KSXX INX
4160 LDA KEYWDS,X
4170 BPL KSXX
4180 BMI SKEX
4190 FKEX INX
4200 LDA KEYWDS,X
4210 BMI KSEX
4220 STA BABUF,Y
4240 JMP FKEX
4250 KSEX AND #$7F
4280 .FILE MATH
Program 6-2. Indisk, Apple Modifications
To create the Apple version of Indisk, change the following lines in Program 6-1:
740 COMOA CMP #$3E;------------------ CHECK FOR OTHER ODD CHARACTERS
760 CMP #$3C
780 CMP #$2B
810 COMO CMP #$2A
830 CPY #255
Program 6-3. Indisk, Atari Modifications
To create the Atari version of Indisk, omit lines 1040-1210 and lines 4090-4260 of Program 6-1 and add or change the following lines:
10 ;ATARI MODIFICATTONS--INDISK
115 JSR LINENUMBFR
610 PAX NOP
760 COMOA CMP #62
780 CMP #60
800 CMP #43
830 COMO CMP #42
1730 LDA $0353
1740 CMP #$03
1751 CMP #136
1752 BEQ INEND
4280 .FILE D:MATH.SRC
Return to Table of Contents | Previous Chapter | Next Chapter