In Part 1 (SYNC 1:5) we saw what a READ statement is, how it functions, how to run a machine language subprogram with the USR function, and how to get your machine code into memory with Basic loader program.
In this part I will give you the machine language READ subroutine, present a few tips on how to get it running, and describe a couple of the features of the Sinclair system that I made use of when I wrote it. If you typed your loader program in last time and SAVEd it, you should be ready to go.
The READ Subroutine
Listing 1a and 1b show you what your TV will look like when you type the subroutine in. Listing 2 has the same information in the column headed “Machine Language” that listings 1a and 1b have, but Listing 2 has much, much more information for those who want to see how this thing works. It has extensive comments on the overall design, more comments on the individual instructions, and the assembly language. When I program, I figure out what I want to do (the overall design) and how to do it. Then I write that in assembly language, and only as a last step do I convert the assembly language to machine language. All of that is in Listing 2.
Listing 1a. READ subroutine, machine language, first par. When you press NEWLINE after typing in the “25” in the bottom line, your screen will go blank for a moment. Then you will see the first three lines shown in Listing 1b, and the 25 will be the first number in the fourth line.
11 08 3F 14 1A F5 13 1A
F5 D1 2F F5 E1 F1 06 08
0F CB 1B 10 FB 2F 06 08
1F CB 1D 10 FB 23 13 13
D5 D5 1A F5 13 1A F5 D1
F1 06 08 1F CB 1B 10 FB
C1 03 03 0A 0F 38 29 13
E5 19 E1 30 09 C1 01 00
3F 04 3E 01 02 C9 1A FE
29 20 EC 13 1A FE 26 20
E6 13 1A FE 39 20 E0 13
1A FE 26 20 DA 3E 01 02
21 00 00 13 1A FE D8 28
"25L"
Listing 1b. A continuation of Listing 1a. Note that the first three lines across the page are the last three lines from Listing 1a. Continue typing with the second number in the fourth line of this listing.
E6 13 1A FE 39 20 E0 13
1A FE 26 20 DA 3E 01 02
21 00 00 13 1A FE D8 28
25 06 3B CB 00 B8 28 18
D6 1C 38 EF FE 0A 30 EB
29 E5 29 29 C1 09 06 08
1F CB 19 10 FB 09 18 DB
C1 C5 03 03 AF 02 E3 EB
E3 C1 C5 06 08 CB 19 1F
10 FB 12 F1 13 12 C9
A READ Revisited
I started this whole discussion in Part 1 by talking about the need for a method to READ numbers out of special program lines into array elements. In the first Listing I showed what a READ might look like if we had one in this Basic. Now that we have a subroutine that performs the same function, I would like to come the full circle and show how I would adapt that hypothetical listing to function with this subroutine. I think Listing 4 says it all. Try adding these lines to your subroutine and its supporting lines! Do not forget the comma in line 70!
Listing 3. Seven lines that can be added to the loader program (Listing 4 in Part 1) to check out the READ subroutine after it has been stored at line 1.
10 REM DATA 1,2,3,4,5,6,7,8,9,
10,11,12
15 FOR J=1 TO 23
20 LET L=USR(16427)
25 PRINT L,U(0),U(1)
30 NEXT J
35 STOP
1200 REM DATA 101,102,103,,
Listing 4. Using a subroutine (located at 16,427 in RAM) to simulate a READ on the Sinclair with a 4K Basic. Notice that line 50 is a REMark. Also, line 30 does not “READ A(J)” but LETs A(J) equal the value returned by the machine routine.
10 DIM A(50)
20 FOR J=0 TO 50
30 LET A(J)=USR(16427)
40 NEXT J
50 REM DATA 174,39,317,255,78,
131,286,228,224,152,158,186,247,
241,85,161,24,145,50,271,38,106,
165,95,313,206,95,261,80,58,259,
296,24,1,178,133,268,41,249,250,
279,294,66,323,179,115,81,66,93,
200,281
60 FOR Z=0 TO 50
70 PRINT A(Z),
80 NEXT Z
I like this set-up because it looks and functions a lot like the ideal that I am trying to emulate. It does have a few differences, however.
Numbers and Non-Numbers
I said before that the subroutine will not function if the word “DATA” is misspelled or if there are spaces between the letters. READ statements in other Basics are usually sensitive to spelling but not to spacing.
This one is like the others in that it ignores spaces between numbers.
••50•REM•DATA•17••4••,3••9,3•17,
2,•••••5••5,••7••8,•(etc.)
This works fine. In fact, it does not even care if the spaces are turned into letters. Even this:
••50•REM•DATA•17WH4AT,3A•9,
317,
2BUNCH5OF5,JU7NK8,•(etc.)
does not bother it. You will not find another READ statement anywhere that will READ that line.
Negative Numbers
Just for a test, you might want to add a few minus signs in front of your numbers.
50 REM DATA -174,-39,-317,255,
78, (etc.)
Shucks! When this is RUN, the subroutine ignores the minus signs just like it ignores the other junk we gave it, and we still get positive numbers. But if we try subtracting each of these numbers from 65,536:
65,536 - 174 = 65,362 65,536 - 39 = 65,497 65,536 - 317 = 65,219 65,536 - 255 = 65,281
and place the results in our line of DATA where the negative numbers were:
50 REM DATA 65362,65497,65219,
65218,78, (etc.)
Eureka! It works! But why does it work?
The answer to that lies in the way a computer handles positive and negative numbers. It uses a system called “two’s complement representation.” If you want to know more, I suggest that you pick up a good book on Z-80 microprocessor programming and look up two’s complement. The first of the two books by Nichols, Nichols and Rony (see References in Part 1) has an excellent six-page discussion of two’s complement.
System Features That the READ Subroutine Uses
In this project, I made use of two system features, which I will describe.
Finding a Variable
You cannot use the READ subroutine without including the following line in your Basic program:
2 DIM U(1)
This line creates an array with two elements in it, U(0) and U(1). The READ sub uses both these elements. But there must first be an array where it expects to find one. It does not matter what letter the array is identified by (it does not have to be “U”), nor does it matter if it has more than two elements (the extra ones will not be used). But there must be an array starting at the very first locations of the space used to store variables.
Once a variable is created, it will reside in the user-variables section of RAM until it is erased by a CLEAR or a RUN. The rule, except for string variables, is that the first variable created is in the first locations of that section. (Strings are erased from earlier locations and moved to the end every time they are changed.) The newest variable always goes to the end of the file.
Now the user-variables section begins where the program ends. This means that the whole section is going to get moved around. That’s right! When you add a line to your program, it gets longer, and the Sinclair system knows that it must move the variables to make room. Likewise, it moves everything together again, to fill the gap, when you take a line out.
But do not think that it does all this blindly. It does not. In fact, it keeps accurate track of where everything is. It reserves ten locations, in the lower part of RAM, to help it do that. Two of these locations store the low part and the high part of the address of the beginning of the first byte OF THE FIRST USER VARIABLE.
My READ subroutine finds that address, presumes the first variable is going to be an array, and goes to the place where the first element would be (if it actually were an array). It expects to find an address there pointing to the place in the program where it has to look for more DATA. It also expects a second element to hold a flag telling it whether that address is inside a line of DATA or not. If not, it knows that it will have to initiate a search for a place farther down in the program where the word “DATA” has been typed.
Then, before it returns to execute the rest of the Basic line, it updates those two things that it thinks are array elements.
As I said, it only thinks that the array is in the right spot. It does not do any checking. The programmer who wants to use this subroutine is going to have to make sure that a two-element array gets placed at the very beginning of the variable file. This is easy to do. Just make sure that the first variable created is the needed array. Then you will know it is in the beginning. Even though the system will move the file around as you make your program larger or smaller, there will always be a pointer (at locations 16392 and 16393) to the beginning of the file and thus to the special array.
DIMension statements create array type variables. LET and FOR statements also create variables some of the time. LET opens space for simple variables when they do not already exist. FOR does the same for the type of variable that controls a FOR-NEXT loop—it creates one when it did not exist before.
Thus, if, when the computer is RUN, it finds the DIMension statement before it finds any FORs or LETs, I know things will function correctly. This means that I make line 2 a DIMension. I could have added some sort of checking routine, but that would have lengthened the subroutine. I would rather put up with the quirk of having to place the thing properly than to make it any bigger than it is.
Error Messages
One added feature is a subroutine to stop the Basic program with an error message when it runs out of DATA.
The first location in RAM is used as a mail box for error messages. Each time it executes a line, the system checks its “mail” to see if any mistakes were made. If it finds the number 255 there, it knows everything is OK and keeps going. If it finds a number from zero through eight, however, there was an error. So it stops the program, adds one to that number, and displays it, together with the line number where the error was found, in the lower left corner of your TV screen.
The location of the mail box is 4000 hex (or 16384 decimal—see Appendix: System Memory Locations in your manual for more details). If you do not type in enough DATA, the subroutine will signal you when it runs out. It will put a 1 in the mail box and the system will stop the Basic. That means you will usually see a 2 followed by a colon and the line number where you were trying to use the USR function. (I have seen it change to a three when I was printing an array element in the same line. For some reason, the system changed it.)
Getting by Without the “Forbidden Codes”
The remainder of the article presumes a knowledge of machine language programming, preferably on the Z80.
As noted Part 1, there are many numbers that cannot be POKEd into line 1 without messing things up. Those are the numbers from 40 hex through 7F hex. This restriction takes certain instructions away from us outright, and places severe limitations on our use of other ones. The instructions that are totally forbidden are the ones whose machine code number falls in the forbidden group. The ones that it restricts are those that sometimes require data in that range.
1) Forbidden Instructions (Single Op Code)
The single byte instructions that cannot be used in this system are transfers from one register to another within the CPU, and the transfers between a register and a memory location when using the HL register pair as the pointer to that location. Fortunately, the accumulator (the A register) can transfer to or from memory using either the BC pair or the DE pair as pointers. Those op codes (operation codes) are not forbidden. They are:
02 for LD (BD),A copy accumulator into memory at address in BC; 0A for LD A,(BC) copy memory at address in BC into accumulator; 12 for LD (DE),A copy accumulator into memory at address in DE; and 1A for LD A,(DE) copy memory at address in DE into accumulator.
They work fine. We can use them to get information between the accumulator and any memory location in the computer. If we could find a way to transfer it between the accumulator and the register that is to be its destination (or soure), then we would have it.
We have two ways of working this. If we want to transfer a value between the accumulator and the high byte of a register-pair, we can put it on the stack and take it off again. For instance, to transfer a value from A to D, we can put the accumulator and flags onto the stack and take it off again into DE.
Example 1.
F5 PUSH AF
D1 POP DE
This copies the accumulator into the D register and the flags into the E register. Going the other way:
Example 2.
D5 PUSH DE
F1 POP AF
copies the D register into the accumulator, but you should remember that it also changes the flags. Normal register transfers do not change the flags.
For other moves we can use the rotate instructions. One rotate will move a single bit from one register into the carry flag, and another rotate will get that bit from the flag into the other register. Then we repeat that operation seven more times on the next seven bits, and we have moved the contents of one register into the other. Each of the following three examples puts a copy of the accumulator into the E register:
Example 3.
5F LD E,A
Example 4.
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
0F RRCA
CB 1B RR E
Example 5.
06 08 LD B,08H
0F LOOP RRCA
CB 1B RR E
10FB DJNZ LOOP
The first example is the most straight forward. It takes up only one byte of memory, but its op code could mess us up.
Example 4, the second one, gets around that problem, but it requires twenty-four bytes of storage.
The final example requires only seven bytes and uses no objectionable op codes. It uses the DJNZ (Decrement and Jump if Not Zero) instruction to repeat the instructions that move the data, one bit at a time, from the A register, through the carry, and into the E register. In order to make the DNJZ instruction work, we must load the B register with the number eight. DNJZ decrements B, checks to see if it is zero, and jumps if it is not. So the loop will be done eight times. Note that this method changes both the B register and the carry flag, so if either holds important information, you will have to save it and restore it afterwards.
2) Forbidden Instructions (Multiple Op Code)
The same restrictions apply to the IX and IY registers as to the HL pair. You cannot use them to load the other CPU registers from memory or vice versa without using one of these numbers. In fact, the second op code in an IX or IY instruction is always the same as the first op code in the similar HL instruction.
The BIT test instructions are all lost because of these restrictions, and so are the INput and OUTput instructions that are distinctive to the Z80. The original 8080 style IN A,(N) and OUT (N),A can still be used.
The only way that I see to replace the BIT test is to rotate the bit to be tested into the carry flag and test the flag. This changes the register or the memory location that you rotate, so you may need to copy the byte into the accumulator and rotate it instead.
To NEG (negate the accumulator two’s complement style) you must CPL (one’s complement) and then add one:
Example 6.
ED 44 NEG
is replaced by:
Example 7.
2F CPL
C6 01 ADD 01H
These two examples leave the same numbers in the accumulator, although the flags will usually be different.
Sixteen-bit subtraction is lost, so when I needed to do one to find out if the search for a line of DATA had taken us beyond the end of the program, I did it the same way that early programmers had to do their subtracts. I took the two’s complement of the subtrahend (the number with the minus in front of it) and I added that to the minuend (the other one).
There are a few other instructions that cannot be used, but I had no need for them, so I did not investigate them. These include rotates that move a whole digit (four bits) at a time and transfers to and from the interrupt (I) register or the refresh (R) register.
3) Forbidden Data
Early in the subroutine, I wanted to load 4008 hex into the DE pair. I could not do that because it would have put a “40” into my REMark line. I had to put 3F08 hex into DE and increment D.
Later, I wanted to test to see if the subroutine had just loaded an EOL marker. That would have meant that it had come to the end of the line of DATA in the Basic program. But the Sinclair uses 76 hex (or 118 decimal) as EOL signals and I could not just write:
Example 8.
FE 76 CP 76 H
because that would have scrambled things. So first I figured out that half of 76 hex is 3B hex. I loaded 3B hex into B, I doubled it by rotating it to the left, and I compared the contents of the accumulator to it.
Example 9.
06 3B LD B,3BH
CB 00 RLC B
B8 CP B
I cannot call a subroutine that I have stored somewhere in RAM, and I cannot jump absolute to any location in RAM without writing one of these numbers. RAM begins at 4000 hex and ends at 43FF hex if you have 1K. So the high-order byte of any address in RAM will be from 40 hex through 43 hex. When I tell it to jump absolute or to call, I must give it an address and that high byte is forbidden. That will not change if I get 16K because it will go from 4000 hex through 7FFF hex. If I find some useful subroutines in the system ROM (Read Only Memory), I can call them as long as the low-order byte is not forbidden. But within any program that I intend to store in a REMark line, I am otherwise limited to the relative style of jump.
The relative jumps are the ones in which you do not tell it what exact address to go to, but how many spaces to forward or backward. The machine takes that number and the present value of the program counter, then generates a computed address, and finally goes to the computed address. Those of us who learned to write machine programs on the 6502 or the 6800 had to learn this relative type of addressing. We could not call if carry or jump if zero or return if parity odd. We had a single, unconditional jump, and a single, unconditional jump to subroutine, and a single, unconditional return. If we did not want the instruction to execute under certain conditions, we would have to put a branching test ahead of it. Then we would avoid the instruction entirely when the conditions were not fulfilled. We were forced to learn how to compute a relative jump. Those of you who have never learned that, can consider this your big headache or your big opportunity!
4) Computing a Relative Jump.
None of the books listed at the end of Part 1 really tells you how to figure a relative jump. So, here goes!
A relative jump can go forward 127 locations or backward 128 locations from the first byte of the first instruction after the jump instruction.
When I compute the displacement for a relative jump, I first leave the space of one byte after the jump instruction. This is where the displacement will go. I put my finger on the byte after that. This is the first instruction after the jump, and I call it byte number zero. A common mistake is to use the jump itself as byte zero and that will get you in trouble.
The second step is to count the number of memory locations from there that I want it to jump when it does jump. (If that is more than 127 forward or 128 backward, I quit and look for another method.)
Third, if the jump is forward, I convert it to hex and that is my relative displacement.
Fourth, if the jump is backward, I subtract the number I got in the second step from 256. My displacement will be the hex version of that difference.
Fifth, I double-check my work by counting the bytes again, usually in hexadecimal this time. I may also check it by putting the segment of code into the computer and running it.
Let’s look at a couple of displacements calculated for the READ subroutine presented earlier in this article. The first is a backward jump (Example 10) and the second is a forward jump (Example 11).
Example 10: Backward Jump
06 08 LD B,08H
0F RRCA
CB 1B RR E
10 ?? DJNZ LOOP
2F CPL
In example 10, when the B register finally decrements to zero, there will be no more jumps and the next instruction will be CPL. So I put my finger on the “2F” and say that it is byte zero.
Second, I count back. The space I have left for the displacement byte (the “??”) is number one, “10” is number two, “1B” is three, “CB” is four, and “OF” is number five. That is the byte which I want it to do next when it does jump, so I am done with step two.
It is a backward jump so step three does not apply.
Step four is to subtract the number I got in step two from 256. That leaves me 251. Then I convert it to hex (see below). That gives me FB. So I take the question marks out of the displacement byte and put in “FB”.
Fifth, I put my finger back on the “2F” which I called byte zero before. This time I call it byte number FB. Then the displacement byte that I just put in will be number FC, the “10” is FD, the “1B” is FE, the “CB” is FF, and the destination (the “OF”) is byte number 100. That is what I wanted to see, because I know that the Z80 CPU is going to do something similar and come to the conclusion that it must execute the “OF” next when it jumps.
Example 11: Forward Jump
30 ?? JR NC,JUMP2
C1 POP BC
01 00 3F LD BC,3F00H
04 INC B
3E 01 LD A,01H
02 LD (BC),A
C9 RET
1A JUMP2 LD A,(DE)
In this case, the ZX80 is to go to the line marked with the label “JUMP2” whenever it does jump. How far is that?
Well, for the first step, I know that the “C1” following the “??” is byte zero. I also know that the “1A” in the bottom line is the destination.
So for the second step, we count. “01” is byte one, “00” is two, “3F” is three, “04” is four, “3E” is five, “01” is six, “02” is seven, “C9” is eight, and “1A” is nine. Nine is the number.
Step three, since nine is the same in hex as it is in decimal (no conversion is needed until the number is ten or larger), I just plug “09” in where I have the “??”.
Step four does not apply, so I check my work and I am done.
5) Converting Decimal to Hexidecimal
I showed you, in Part 1, that it is easy to convert from hex to decimal. This time I need to show you how to go the other way. But in order to tie it all together, I will review briefly.
First, remember that there are sixteen numbers in the hex system:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, & F
Let’s take the number FB in hex and find out what it is in decimal. We see that an F is equal to our number fifteen. Since every increment in that column is equal to sixteen in the decimal system, an F there equals fifteen times sixteen, or 240. Now the B in the right column is equal to our eleven, and each increment in that column is worth only one, so that is eleven times one, or eleven. Add them together and we have 251.
(Fx16)+(Bx1)=(15×16)+(11×1)=251
End of review. Now let’s go the other way—from decimal to hex. If I have a number small enough to fit into a two digit hex number after conversion, then I can make the switch with a single division by sixteen. (255 is the largest decimal number that will work.) In example 10, I ended up needing to convert 251. So I divided:
251/16 = quotient of 15 and remainder of 11
Then, if the quotient or the remainder is ten or more, I substitute the number between A and F that it corresponds to. 15 is the Fth number in the hex sequence, and 11 is the Bth one. The remainder goes in the ones column and the quotient goes in the next (the “sixteens” column. Therefore, the hex equivalent of 251 will be FB.
If you want to practice doing a few jump displacements, you will notice that in the comments column in Listing 2 I give every displacement I computed both in the positive or negative numbers from the steps two, and in the final hexidecimal number.
This explanation goes deep enough to help you convert your jump displacements. The programming texts that I already recommended explain the conversion process in more detail. They will help you convert larger numbers, like addresses, if you need to.
Conclusion
This article has covered a lot of ground. I have given you an implementation of the standard Basic statement “READ” for your Sinclair or MicroAce. Part of my reason for writing this article was to give you an idea of what it is like to program in machine language on these machines. I mainly concentrated on the special techniques needed for that task, and I left alone the information that is already well covered in the programming texts.
I hope you have enjoyed it. Good READing! Good programming.
Getting It to Work
1. LOAD the loader program from Part 1.
a. The REMark line has been numbered line 1 to make sure it stays in the same location in RAM.
b. There need to be as many minus signs in line 1 as there are bytes in the program plus maybe one or two extra for safety. This program is 159 bytes long.
c. SAVE the loader for your own experiments in programming.
2. Now, RUN it. Your screen will go blank except for the cursor in quotation marks in the upper left corner.
a. It is looking for a hexadecimal number (0 thru 9 or A thru F) or for the command to stop which is an “S”.
b. Always give it two numbers! If you want to enter “8” type “08” and not “8”! Press NEWLINE when you are satisfied with your entry.
c. After you enter the 105th byte, the program will take a moment to erase the top ten lines, and will reprint bytes numbered 81 thru 105. After the 159th byte, the program will automatically stop.
d. To correct an error (ten bytes back for example):
1) Stop the program by typing “S” and NEWLINE;
2) Change the address by typing LET I=I-10 and NEWLINE;
3) Press CONTinue and NEWLINE;
4) When the error has been corrected, you can get back to where you were by using a similar procedure to add nine to I.
3. Now enter the subroutine shown in Listing 1a, and in the bottom seven lines of Listing 1b. The top three lines shown in Listing 1b repeat the bottom three in Listing 1a. This will be helpful if you write your own machine programs.
a. Check your code carefully against the listings. The Sinclair system will not check for errors in machine code like it does in Basic. One error could cause you to bomb and lose the whole program.
b. SAVE a copy right now. If it does bomb because of an error that you misssed, you will not have to start all over.
4. Now it is time to make sure it works.
a. The lines in Listing 3 can be added to test it.
1) The keyword REM in lines 10 and 1200 allows us to type the word DATA, which the subroutine needs.
2) DATA lines have no comma at the end. I put two at the end of line 1200 to show how that messes things up.
b. When you RUN it, check the display as follows:
1) The numbers in the left column are the DATA that was just READ out of lines 10 and 1200. Note the extra zeros at the bottom. These are caused by the commas at the end of line 1200.
2) Each number in the middle column is the address of the comma or EOL (End-Of-Line) marker following the piece of DATA to the left of it in the same row. If your addresses are not the same as mine, do not worry about it.
3) The numbers in the right column are the flags. A one says it came back because it found a comma and there are more numbers in the line. A zero says the subroutine found an EOL and must look for another line of DATA if it is called again.
4) The “2:20” in the lower corner of the screen says that the subroutine ran out of DATA. We asked it to do twenty-three READ operations, but only gave it eighteen numbers to READ.
c. Did your display do all that? If it did not function properly, the problem could be with the Basic or with the machine code.
1) In the Basic the word “DATA” must be spelled correctly with no spaces between the letters. Also, commas between the numbers must be commas and not periods.
2) If that is not the problem problem, check the machine code again.
5. If it works right, you can take out all the lines numbered ten and higher.
a. What you will be left with is the bar subroutine in line 1, the next two lines which it needs to function properly (line 2 and line 3), and line 5, which is not essential but will help to remind you where to find the start of subroutine.
b. If you SAVE a copy or two of this, you will have it ready when you want to convert a program that needs a READ.
Listing 2
Listing 2. The READ subroutine—assembly and machine languages with addresses and comments. (Only relative jumps are used, so addresses are re-locatable).
Listing of a subroutine that simulates a READ statement with the Sinclair 4K Basic. Prepared June 7, 1981, from a hand-assembled source by Edward A. Kennedy.
Point the DE register-pair to 4008 hex (16392 decimal). The 16-bit number stored there at the next location is the vector pointing to the beginning of the user’s variable file. Copy it into the DE pair and put its twos-complement into HL.
Location Machine Assembly
Decml / Hex Language Labels Language Comments
16427 / 402B 11 08 3F LD DE,3F08H ;Put 4008H
16430 / 402E 14 INC D ; into DE.
16431 / 402F 1A LD A,(DE) ;Get lo byte of vector
16432 / 4030 F5 PUSH AF ; and save it.
16433 / 4031 13 INC DE
16434 / 4032 1A LD A,(DE) ;Get hi byte
16435 / 4033 F5 PUSH AF ; and transfer it
16436 / 4034 D1 POP DE ; to D.
16437 / 4035 2F CPL ;And its ones complement
16438 / 4036 F5 PUSH AF ; goes
16439 / 4037 E1 POP HL ; to H.
16440 / 4038 F1 POP AF ;Retrieve the lo byte
16441 / 4039 06 08 LD B,08H ; rotate it circular
16443 / 403B 0F LOOP RRCA ; back into itself
16444 / 403C CB 1B RR E ; and into E
16446 / 403E 10 FB DJNZ LOOP ;(FBH = -5 decimal)
16448 / 4040 2F CPL ;And its ones-complement
16449 / 4041 06 08 LD B,08H
16451 / 4043 1F LOOP0 RRA ; rotate that
16452 / 4044 CB 1D RR L ; into L
16454 / 4046 10 FB DJNZ LOOP0 ;(FBH = -5 decimal)
16546 / 4048 23 INC HL ;Add one to get twos-complement
16457 / 4049 13 INC DE ;Move DE past the header
16458 / 404A 13 INC DE ; of the first variable (the U array)
16459 / 404B D5 PUSH DE ; and save two copies
16460 / 404C D5 PUSH DE ; for later use
This should be an array element, and the 16-bit value stored here should point to the next location where we want to look for DATA. We copy it into the DE pair.
16461 / 404D 1A LD A,(DE) ;Load lo byte
16462 / 404E F5 PUSH AF ; and shove it
16463 / 404F 13 INC DE
16464 / 4050 1A LD A,(DE) ;Load hi byte
16465 / 4051 F5 PUSH AF ; and move it
16466 / 4052 D1 POP DE ; to D
16467 / 4053 F1 POP AF ;Retrieve lo byte
16468 / 4054 06 08 LD B,08H
16470 / 4056 1F LOOP1 RRA ; and rotate it
16471 / 4057 CB 1B RR E ; into E
16473 / 4059 10 FB DJNZ LOOP1 ;(FBH = -5 decimal)
Retrieve the array pointer from the stack, add two to it so that it points to the next element (the flag), copy that into A, and test it. A one indicates we are in a line of DATA.
16475 / 405B C1 POP BC ;Pop pointer
16476 / 405C 03 INC BC ;Point it to second
16477 / 405D 03 INC BC ; array element
16478 / 405E 0A LD A,(BC) ;Load flag byte
16479 / 405F 0F RRCA ;Rotate flag into carry—if one,jump
16480 / 4060 38 29 JR C,JUMP3 ;(29H = +41 dicimal) to get DATA
If flag was zero, search for sequence of letters D-A-T-A. Also test to see that the search has not gone beyond the end of the program. If past end, leave an error message and return. If DATA found, change flag to one.
16482 / 4062 13 JUMP1 INC DE ;Move to next location
16483 / 4063 E5 PUSH HL ;Subtract address of end of program
16484 / 4064 19 ADD HL,DE ; by adding its twos-complement
16485 / 4065 E1 POP HL ; —preserve HL in process
16486 / 4066 30 09 JR NC,JUMP2 ;(09H = +9D) Still OK if no carry
16488 / 4068 C1 POP BC ;Else straighten the stack,
16489 / 4069 01 00 3F LD BC,3F00H
16492 / 406C 04 INC B ; leave error message
16493 / 406D 3E 01 LD A,01H ; (a one)
16495 / 406F 02 LD (BC),A ; at 4000H,
16496 / 4070 C9 RET ; and go back
16497 / 4071 1A JUMP2 LD A,(DE) ;Search comes here if still in program
16498 / 4072 FE 29 CP 29H ;Look for "D"
16500 / 4074 20 EC JR NZ,JUMP1 ;(ECH = -20D) and go back if not
16502 / 4076 13 INC DE
16503 / 4077 1A LD A,(DE) ;Else look next for "A"
16504 / 4078 FE 26 CP 26H
16506 / 407A 20 E6 JR NZ,JUMP1 ;(E6H = -26D) and go back if not
16508 / 407C 13 INC DE
16509 / 407D 1A LD A,(DE) ;Else look next for "T"
16510 / 407E FE 39 CP 39H
16512 / 4080 20 E0 JR NZ,JUMP1 ;(E0H = -32D) and go back if not
16514 / 4082 13 INC DE
16515 / 4083 1A LD A,(DE) ;Else next look for "A"
16516 / 4084 FE 26 CP 26H
16518 / 4086 20 DA JR NZ,JUMP1 ;(DAH = -38D) and go back if not
16520 / 4088 3E 01 LD A,01H ;Found? BC still points to flag in
16522 / 408A 02 LD (BC),A ; user array—change flag
Come here (from either of two places) after deciding that we have a line of DATA. Put zero in sum—the HL register pair.
16523 / 408B 21 00 00 JUMP3 LD HL,0000H ;Zero sum
Get next character. Jump to return routine when comma or End-Of-Line is found. If it is a number, multiply sum by ten, add number to it, and come back for next character.
16526 / 408E 13 JUMP4 INC DE
16527 / 408F 1A LD A,(DE) ;Load next character
16528 / 4090 FE D8 CP D8H ;Check for comma
16530 / 4092 28 25 JR Z,JUMP6 ;(25H = +37 decimal)
16532 / 4094 06 3B LD B,3BH ;Else put End-Of-Line
16534 / 4096 CB 00 RLC B ; into B
16536 / 4098 B8 CP B ; and check for it
16537 / 4099 28 18 JR Z,JUMP5 ;(18H = +24 decimal)
16539 / 409B D6 1C SUB 1CH ;Convert it to a number (SUB 28D)
16541 / 409D 38 EF JR C,JUMP4 ;(EFH = -17D) Less than zero? Go back
16543 / 409F FE 0A CP 0AH ;Is it a letter? (Greater than nine?)
16545 / 40A1 30 EB JR NC,JUMP4 ;(EBH = -21 decimal) Then go back
16547 / 40A3 29 ADD HL,HL ;Multiplication by ten
16548 / 40A4 E5 PUSH HL ; requires six commands
16549 / 40A5 29 ADD HL,HL
16550 / 40A6 29 ADD HL,HL
16551 / 40A7 C1 POP BC
16552 / 40A8 09 ADD HL,BC
16553 / 40A9 06 08 LD B,08H ;Now rotate
16555 / 40AB 1F LOOP2 RRA ; latest number into C
16556 / 40AC CB 19 RR C ; (B will be zero when loops done)
16558 / 40AE 10 FB DJNZ LOOP2 ;(FBH = -5 decimal)
16560 / 40B0 09 ADD HL,BC ;Add it in then go get next character
16561 / 40B1 18 DB JR JUMP4 ;(DBH = -37 decimal)
If subroutine comes here, DATA is already in HL register pair. The return has two parts. The first part changes the flag in the second array element when an End-Of-Line was found (not a comma).
16563 / 40B3 C1 JUMP5 POP BC ;Top stack entry points at first array
16564 / 40B4 C5 PUSH BC ; element. Increment it twice
16565 / 40B5 03 INC BC ; to point at second.
16566 / 40B6 03 INC BC
16567 / 40B7 AF XOR A ;Put zero in A
16568 / 40B8 02 LD (BC),A ; and then in flag.
Second part of return puts address of the End-Of-Line or comma into first array element and then returns. The triple swap in the first three lines moves the address of the comma or EOL from DE to the stack and the address of the first array element from the stack to DE while leaving the DATA in HL intact.
16569 / 40B9 E3 JUMP6 EX (SP),HL ;DATA, DATA,
16570 / 40BA EB EX DE,HL ; who's got
16571 / 40BB E3 EX (SP),HL ; the DATA?
16572 / 40BC C1 POP BC ;Copy lo byte of comma or EOL
16573 / 40BD C5 PUSH BC ; address into C,
16574 / 40BE 06 08 LD B,08H
16576 / 40C0 CB 19 LOOP3 RR C ; move it
16578 / 40C2 1F RRA ; to A,
16579 / 40C3 10 FB DJNZ LOOP3 ;(FBH = -5D)
16581 / 40C5 12 LD (DE),A ; and store it.
16582 / 40C6 F1 POP AF ;Retrieve hi byte
16583 / 40C7 13 INC DE
16584 / 40C8 12 LD (DE),A ; and store it
16585 / 40C9 C9 RET ;Return to the BASIC with DATA
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.