In the last section, I gave the locations where TOP, PAGE, HIMEM and LOMEM are stored. There is one important location missing from that list, however, The User Guide tells us that the variables are stored just above the text of the current program, and then grow upwards. Thus, there should be a pointer to the top of the variables, or the next free location after the variables.
The first step in our exploration is thus to find where that pointer is stored. I reasoned that when no variables existed, the free space pointer should be the same as LOMEM. Thus, I used this program to find all the locations which contained the same number as LOMEM:
10 FOR T%=&00 TO &FF 20 IF (!T% AND &FFFF)=LOMEM THEN PRIN T ~T% 30 NEXT T% 40 END RUN 0 2 12 17
Then I declared a variable, to see which locations remained:
5 ASD=234 RUN 0 12 PRINT (!2 AND &FFFF) 3786 PRINT (!17 AND &FFFF) 49407 PRINT LOMEM 3776
After running the program again, it became apparent that the free space pointer must be stored at either location 2, or location &17. So I tested the values in these two locations, and concluded that the free space pointer must be stored at address 2, since location 17 contained a number that was far too big.
The next step was to construct a program to list out the contents of memory between TOP and the free space pointer, since this is the area where variables are stored. The program I used was:
1000 @%=4 1010 FOR T%=TOP TO (!2 AND &FFFF) 1020 PRINT ~T%,~?T%; 1030 A%=?T% MOD 128 1040 IF A%>31 THEN PRINT "--->";CHR$(A% ); 1050 PRINT 1060 NEXT T%
(I should mention why I am continually using single character integer variables. As you know, these variables are not cleared by RUN or CLEAR. It turns out that they are stored in a special area of memory, from &400 to &46C. These addresses were found by looking at the lower portion of memory while defining some integer variables. Thus, as they are stored in a special area of memory, they do not affect the free space pointer. This is useful where, as in this case, we are looking at a few variables. Another point to notice about the storage of the integer variables is that they are stored at fixed locations, and thus may be located very quickly. @% is stored first, followed by A% to Z%. A four byte binary format is used for storage).
The program prints out all addresses in hex, and the number stored there. If the contents of the location is not a control code, the ASCII representation is displayed too.
Having designed the program I had to give it a variable to work on:
10 LET A=23 RUN E83 38--->8 E7F 17 E84 0 E80 0 E85 0 E81 0 E86 0 E82 85 E87 3E
As you can see from the sample run, the letter 'A' does not appear in the variable storage area. This was a little odd. I tried with a longer variable name:
10 LET ASDFGHJKL=3.1415926535897 E94 D E95 0 E96 53--->S E97 44--->D E98 46--->F E99 47--->G E9A 48--->H E9B 4A--->J E9C 4B--->K E9D 4C--->L E9E 0 E9F 82 EA0 49--->I EA1 F EA2 DA--->Z EA3 A2--->" EA4 0
Now you can see the entire name of the variable, except for the first letter. You can also see the five byte floating point representation of PI starting at address &E9F. This format should be explained.
For this explanation, I quote from Toni Baker's 'Mastering machine code on your ZX81 or ZX80', published by Interface:
"Here is a list of the first few integers as five byte floating point numbers:
Decimal | Floating point representation | ||||
0 | 00 | 00 | 00 | 00 | 00 |
1 | 81 | 00 | 00 | 00 | 00 |
2 | 82 | 00 | 00 | 00 | 00 |
3 | 82 | 40 | 00 | 00 | 00 |
4 | 83 | 00 | 00 | 00 | 00 |
5 | 83 | 20 | 00 | 00 | 00 |
6 | 83 | 40 | 00 | 00 | 00 |
7 | 83 | 60 | 00 | 00 | 00 |
8 | 84 | 00 | 00 | 00 | 00 |
9 | 84 | 10 | 00 | 00 | 00 |
10 | 84 | 20 | 00 | 00 | 00 |
Decimal | Floating point representation | ||||
-1 | 81 | 80 | 00 | 00 | 00 |
-2 | 82 | 80 | 00 | 00 | 00 |
-3 | 82 | C0 | 00 | 00 | 00 |
-4 | 83 | 80 | 00 | 00 | 00 |
-5 | 83 | A0 | 00 | 00 | 00 |
-6 | 83 | C0 | 00 | 00 | 00 |
-7 | 83 | E0 | 00 | 00 | 00 |
-8 | 84 | 80 | 00 | 00 | 00 |
"Knowing how the floating point representation is built up will slightly help your understanding of the arithmetic processes, so I will give here a brief explanation of how to turn decimal numbers into a floating point representation numbers. It only takes a few simple steps.
"STEP ONE: If the number is zero, then its floating point representation is 00 00 00 00 00.
"STEP TWO: Ignoring the sign of the number, write it in binary (but without any leading zeros). For example:
7 | 111 |
-10 | 1010 |
-4.25 | 100.01 |
0.325 | 0.011 |
"STEP THREE: Is to work out a quantity called the EXPONENT. This is done as follows: if the part of the number to the left of the binary point is not zero then the exponent is the number of digits to the left of the point. If the number to the left of the point is zero and the first digit after the decimal point is one, then the exponent is zero. If the part of the number to the number to the left of the point is zero and the first digit after the point is zero, then count the number of zeros to the right of the point up to the first 1 -- the exponent is minus this number. The first byte is &80 plus the exponent.
Decimal | Binary | Exponent | First byte |
7 | 111 | 3 | 83 |
-10 | 1010 | 4 | 84 |
-4.25 | 100.01 | 3 | 83 |
0.325 | 0.011 | -1 | 7F |
7 | 1110 0000 0000 0000 0000 0000 0000 0000 |
-10 | 1010 0000 0000 0000 0000 0000 0000 0000 |
-4.25 | 1000 1000 0000 0000 0000 0000 0000 0000 |
-0.325 | 1100 0000 0000 0000 0000 0000 0000 0000 |
7 | 0110 0000 0000 0000 0000 0000 0000 0000 |
-10 | 1010 0000 0000 0000 0000 0000 0000 0000 |
-4.25 | 1000 1000 0000 0000 0000 0000 0000 0000 |
0.325 | 0100 0000 0000 0000 0000 0000 0000 0000 |
7 | 83 60 00 00 00 |
-10 | 84 A0 00 00 00 |
-4.25 | 83 88 00 00 00 |
0.325 | 7F 40 00 00 00 |
But where is the first letter of the name? And what are the first two bytes for? (The last byte, &17, is present because the free space pointer points to the next free location, and so the program includes the first free location in the printout.)
I reckoned that the first letter was stored somewhere else in memory, so I tried the following:
CLEAR MODE 4 VDU 28,0,0,0,0 VDU 23,0,12,0;0;0;0;0; LET ZXC=23 LET X=234 LET Y=234 LET A=235 LET fdghfg=23
If you look at the third line down the screen, towards the right, as you type in the assignment statements, you should see some alteration in the byte patterns appearing. Try CLEARing, and then creating variables starting with each letter of the alphabet. You should see an area changing from black to apparently random bytes, growing to the right. If you have a very clear TV you should see that each assignment adds two bytes to the list. The other thing to notice is that each letter of the alphabet (upper and lower case) has two locations dedicated to it. It turns out that the location assigned to A has address &482. One can then derive the formula (&400+ASC(A$)*2) to give the address of the two bytes associated with the letter in A$.
Now return to mode 7, and try the following:
MODE 7 CLEAR 10 LET A=23 RUN E7F F1--->q E80 0 E81 0 E82 85 E83 38--->8 E84 0 E85 0 E86 0 E87 20---> PRINT ~(!&482 AND &FFFF) E7F
Does the number &E7F, given in the contents of the locations assigned to the letter 'A', ring any bells? It's the first address used by the storage of the variable.
Thus, the computer keeps a table of two bytes per initial letter of each variable, starting at &482 for the letter 'A', and the address in this table gives the start of the variable with this starting letter. Zero in this table means that no variable starts with that letter. But, what happens if two or more variables start with the same letter?
The only thing to do is to create another variable starting with the same letter, and see how it is stored:
20 LET AF=23 RUN E8B 93 E8C E E8D 0 E8E 85 E8F 38--->8 E90 0 E91 0 E92 0 E93 FF---> E94 0 E95 46--->F E96 0 E97 85 E98 38--->8 E99 0 E9A 0 E9B 0 E9C 0
We can assume that the contents of locations &482 and &483 are &E8B. The storage of the first variable is the same as before, apart from some new values in the previously redundant initial two bytes. You may notice that the address in these two bytes is the start address of the block describing the second variable. So a useful new hypothesis would be that (in addition to the points outlined above), if the contents of the first two bytes of the block are less than 256, the current block is the last variable starting with that letter, and if the two bytes contain a number greater than 255, that the number is the address of the next variable with the same initial letter.
This arrangement is a great deal more powerful than that commonly employed in computers of this type. Most microcomputers employ a free space pointer, and just place each new variable onto the end of the list. Thus when the computer has to find the value of a variable, it has to search all the way through the list until it finds the one it wants. The BBC computer only has to search through those that share the same starting letter. You may like to see if you can get a speed reduction in the running of the program by making all the variables used start with different letters. It is possible to get a 10% reduction in speed by doing this. But, real variables are only a small part of the story. We have to investigate strings, multi-character integer variables, and all the different types of array. We'll start with integer variables with long names.
I added these two lines to the original program, and obtained these results:
DELETE 10,20 10 LET AA%=23 20 LET AB%=24 RUN E8E 97 E8F E E90 41--->A E91 25--->% E92 0 E93 17 E94 0 E95 0 E96 0 E95 0 E96 0 E97 85 E98 0 E99 42--->B E9A 25--->% E9B 0 E9C 18 E9D 0 E9E 0 E9F 0 EA0 0 PRINT ~(!&482 AND &FFFF) E8E
The first point to notice is that not only is the name of the variable minus the first character stored, but the percentage sign is stored too. The sequence is, as before, terminated by zero, preceding the four byte binary representation of the integer. These four bytes may be interrogated with the word indirection operator, (!).
You will also notice that the table is used in the same way as the real variables, and that the link bytes are used in the same way. We can now progress onto string variable storage. I added these two lines to the program, with the following results:
DELETE 10,20 10 LET AB$="JJ" 20 LET AC$="HH" RUN E92 9D E93 E E94 24--->B E95 24--->$ E96 0 E97 9B E98 E E99 2 E9A 2 E9B 4A--->J E9C 4A--->J E9D 0 E9A 2 E9B 4A--->J E9C 4A--->J E9D 0 E9E 0 E9F 43--->C EA0 24--->$ EA1 0 EA2 A6--->& EA3 E EA4 2 EA5 2 EA6 48--->H EA7 48--->H EA8 0
The two bytes at &E92 point to the next string variable, seeing though they both begin with 'A', so the linking appears to be the same as used in integer and real variables. The next two bytes are, again, the name of the variable, minus the first letter, which is stored in the table at &482. Next comes the zero, to mark the end of the name. The next two bytes form an address which points to the contents of the variable, in this case the two 'J's at &E9B. The two 2 s would appear to be the length of the string, but why is it there twice? The tree is again terminated by the two zero bytes for the address of the next variable starting with 'A'.
A little experiment was called for to see which of the two bytes (which apparently held the length of the string) were used by the LEN function.
PRINT LEN(AB$) 2 ?&E99=1 PRINT LEN(AB$) 2 ?&E9A=1 PRINT LEN(AB$) 1 ?&E99=12 PRINT LEN(AB$) 1
The first thing I did was check that the length of the variable was indeed two, and as you can see, it was.
Next, I tried altering the byte in &E99, and printed the length of the string. It remained at two, indicating that the LEN function was not getting its data from the first byte of the two holding the length. The second byte was then altered, and the length printed. It had now changed, so I knew that the length of the string was stored in the second of the two bytes. (The section in The User Guide on CALL reveals that the first length byte gives the number of bytes allocated.)
Before going on to arrays, here is a short re-cap of the points so far mentioned. To store the value of a variable, the computer goes through the following steps:
STEP ONE: Take the ASCII code of the first letter of the name. Work out the address associated with it from the formula 'address=&400+ascii_code*2'.
STEP TWO: Extract the address stored in that location.
STEP THREE: If the address is zero, store the value of the free space pointer in the location. Then go to step six.
STEP FOUR: Go to the address.
STEP FIVE: Go to step two.
STEP SIX: Place the block describing the variable starting at the address in the free space pointer, using zero for the first two bytes, since this is the last variable with that starting letter. Update the free space pointer with the next free location.
You may have to read through that a number times before it is clear. The blocks for each type of variables are:
REAL VARIABLES
* Two bytes, for the address of the next variable of any type with the same starting letter.
* Any remaining letters of the name, besides the first one.
* A zero-byte.
* Five bytes for the value of the variable.
INTEGER VARIABLES
* Two bytes, for the address of the next variable with the same starting letter.
* Any remaining letters of the name, including the percentage sign.
* Zero byte.
* Four byte value of the variable.
STRING VARIABLES
* Two bytes, pointing to any other variables with the same initial letter.
* Any other letters of the name, including the $ sign.
* A zero byte.
* Two bytes, containing the address of the contents of the variable.
* The number of bytes allocated to the string.
* Length of string.
* String data.
But, looking back at the printout, what happens if we adjust AB$ to have three letters in it? There isn't room to put the three letters in place of the two 'J's. So let's see what happens:
30 LET AB$="123" RUN EA2 AD--->- EA3 E EA4 42--->B EA5 24--->$ EA6 0 EA7 B8--->8 EA8 E EA9 3 EAA 3 EAB 4A--->J EAC 4A--->J EAD 0 EAE 0 EAF 43--->C EB0 24--->$ EB1 0 EB2 B6--->6 EB3 E EB4 2 EB5 2 EB6 48--->H EB7 48--->H EB8 31--->1 EB9 32--->2 EBA 33--->3 EBB 66--->f
As you can see, the new value of AB$ has been placed at the top of the variable area, with the two 'J's now being redundant. The two address bytes have been updated to cope with the contents of the variable moving around.
Having done that, let's see what happens if a string is made to have different contents of the same length, or a shorter length.
40 LET AB$="#$%" RUN EB2 BD--->= EB3 E EB4 42--->B EB5 24--->$ EB6 0 EB7 C8--->H EB8 E EB9 3 EBA 3 EBB 4A--->J EBC 4A--->J EBD 0 EBE 0 EBF 43--->C EC0 24--->$ EC1 0 EC2 C6--->F EC3 E EC4 2 EC5 2 EC6 48--->H EC7 48--->H EC8 23---># EC9 24--->$ ECA 25--->% ECB 0
As you can see, the previous contents are overwritten.
A conclusion to be drawn from this is that if you make all string variables as long as you are ever likely to need right at the start of each program using the function STRING$, you will find the computer never needs to find extra storage space for its contents as it increases. This is equivalent to dimensioning strings on other computers.
DELETE 10,40 10 DIM A$(2) 10 DIM ASDF$(2) RUN E83 F1--->q E84 0 E85 53--->S E86 44--->D E87 46--->F E88 24--->$ E89 28--->( E8A 0 E8B 3 E8C 3 E8D 0 E8E 0 E8F 0 E90 0 E91 0 E92 0 E93 0 E94 0 E95 0 E96 0 E97 0 E98 0 E99 0 E9A 24--->$
The first bytes are, as we would expect, pointers to the next variable starting with the letter 'A'.
Then we get the rest of the letters of the name, including the dollar symbol and the opening bracket of the array.
This zero byte signifies the end of the sequence.
The rest of the sequence is hard to work out, so I fill up the array, and then re-ran the program. (I unfortunately forgot the name of the array in mid-type, as the printout testifies!)
20 LET A$(0)="A" 30 LET A$(1)="B" 40 LET A$(2)="C" LIST,999 10 DIM ASDF$(20 20 LET A$(0)="A" 30 LET A$(1)="B" 40 LET A$(2)="C" 20 LET ASDF$(0)="A" 30 LET ASDF$(1)="B" 40 LET ASDF$(2)="C" REM WHOOPS ! RUN EBC 4 EBD 0 EBE 53--->S EBF 44--->D EC0 46--->F EC1 24--->$ EC2 28--->( EC3 0 EC4 3 EC5 3 EC6 0 EC7 D3--->S EC8 E EC9 1 ECA 1 ECB D4--->T ECC E ECD 1 ECE 1 ECF D5--->U ED0 E ED1 1 ED2 1 ED3 41--->A ED4 42--->B ED5 43--->C ED6 0
The two threes are the number of elements of the array. There does not, however, appear to be any indicator of the number of dimensions.
Next comes three blocks containing the address of each element, and its length, again written twice. It is safe to assume that the second length indicator is the one used by the LEN function, and the first is the number of bytes allocated.
The next step was to look at a real array.
DELETE 10,40 10 DIM ASD(2) 20 LET ASD(0)=PI 30 LET ASD(1)=PI 40 LET ASD(2)=P 40 LET ASD(2)=PI RUN EAE 1A EAF 0 EB0 53--->S EB1 44--->D EB2 28--->( EB3 0 EB4 3 EB5 3 EB6 0 EB7 82 EB8 49--->I EB9 F EBA DA--->Z EBB A2--->" EBC 82 EBD 49--->I EBE F EC0 A2--->" EC1 82 EC2 49--->I EC3 F EC4 DA--->Z EC5 A2--->" EC6 0
The format would appear to be similar to the string array, except that the five bytes describing each element appear instead of the blocks of address and length data about each element. The data starts at address &EB7 in the example.
There still is no noticeable way of telling the number of dimensions.
I next turned to integer, two dimensional arrays.
DELETE 10,40 10 DIM ASD%(1,1) 20 ASD%(0,0)=123 30 ASD%(1,1)=&7FFFFFFF 40 ASD%(1,0)=1 50 ASD%(1,1)=&01020304 RUN ED6 1A ED7 0 ED8 53--->S ED9 44--->D EDA 25--->% EDB 28--->( EDC 0 EDD 5 EDE 2 EDF 0 EE0 2 EE1 0 EE2 7B--->{0 EE4 0 EE5 0 EE6 FF---> EE7 FF---> EE8 FF---> EE9 7F---> EEA 1 EEB 0 EEC 0 EED 0 EEE 4 EEF 3 EF0 2 EF1 1
The general format appears familiar, except the block between the data and zero byte indicating the end of the name. What has previously been a three, has changed to a five. After some experimentation I concluded that this byte contains 2*n+1, where n is the number of dimensions of the array. This holds true for any type of array. The next four bytes are the number of elements in each of the two dimensions. Then we get the familiar four byte data for each element.
The program I used to test my hypothesis about the 2*n+1 formula was this one:
DELETE 10,50 10 DIM KJ%(1,1,1,1,1) RUN E87 3B--->; E88 0 E89 4A--->J E8A 25--->% E8B 28--->( E8C 0 E8D 9 E8E 2 E8F 0 E90 2 E91 0 E92 2 E93 0 E94 2 E95 0 E96 0 E97 0 E98 0 E99 0 E9A 0 E9B 0 E9C 0 E9D 0 E9E 0 E9F 0 EA0 0 EC9 0 ECA 0 ECB 0 ECC 0 ECD 0 ECE 0 ECF 0 ED0 0 ED1 0 ED2 0 ED3 0 ED4 0 ED5 0 ED6 1A
As you can see, for reasons of space conservation, I have left out a large chunk in the middle of the prinout.
The array has four dimensions, and the byte is 9, which measures up nicely with the formula.
While musing on the speed of the BBC Computer I ran the following series of experiments.
10 GOTO 100 20 DEF PROCHELLO 30 PRINT "HELLO" 40 ENDPROC 100 PROCHELLO RUN HELLO 200 PROCHELLO
The program above calls a procedure to print the word 'HELLO' twice.
The next step was to find the address of the word 'HELLO' in line 20.
FOR T%=PAGE TO PAGE+19:P. ?T%:N. 13 3584 0 3585 10 3586 11 3587 32 3588 229 3589 32 3590 141 3591 68 3592 100 3593 64 3594 13 3595 0 3596 20 3597 13 3598 32 3599 221 3600 32 3601 242 3602 72 3603 PRINT ASC("H") 72 PRINT ~3603 E13
Given that the ASCII code for 'H' is 72, the start of the word is address 3603 or &E13.
To test this, I placed the code for 'A' into the start of the word, and printed out the program:
?&E13=65 LIST 10 GOTO 100 20 DEF PROCAELLO 30 PRINT "HELLO" 40 ENDPROC 100 PROCHELLO 200 PROCHELLO ?&E13=72 101 ?&E13=65 RUN HELLO HELLO
The 'A' was then replaced with an 'H'.
A line was inserted between the two calls to the procedure, to change the 'H' to an 'A'. The program ran perfectly, even though at the second call, PROCHELLO did not exist!
Listing the program confirmed this:
10 GOTO 100 20 DEF PROCHELLO 30 PRINT "HELLO" 40 ENDPROC 100 PROCHELLO 101 ?&E13=65 200 PROCHELLO ?&E13=72 LIST 10 GOTO 100 20 DEF PROCHELLO 30 PRINT "HELLO" 40 ENDPROC 100 PROCHELLO 101 ?&E13=65 200 PROCHELLO
The next step was to replace the 'A' again with an 'H'.
There are many conclusions that can be drawn from the above points. The first is that after the first call has been made to a procedure, the name of the procedure in the DEF statement does not matter. Thus the computer is storing the address of PROCHELLO, together with its name in some place in its memory. It was a safe bet that this area was the variable storage area, so after I had dissected the variable storage, discussed earlier, I started to explore the storage of procedures, and functions, presuming that the function mechanism is the same as the procedure mechanism.
I used the program I presented before to list out the variable area, but added a procedure definition:
2000 DEF PROCHELLO 2010 PRINT "HELLO" 2020 ENDPROC 10 PROCHELLO 1070 END LIST 10 PROCHELLO 1000 @%=4 1010 FOR T%=TOP TO (!2 AND &FFFF) 1020 PRINT ~T%,~?T%; 1030 A%=?T% MOD 128 1040 IF A%>31 THEN PRINT "--->";CHR$(A% ); 1050 PRINT 1060 NEXT T% 1070 END 2000 DEF PROCHELLO 2010 PRINT "HELLO" 2020 ENDPROC RUN HELLO EA6 29--->) EA7 0 EA8 48--->H EA9 45--->E EAA 4C--->L EAB 4C--->L EAC 4F--->O EAD 0 EAE 90 EAF E EB0 0
When the program is run, the variable list area suprisingly holds the whole name of the procedure, starting at &EA8. Perhaps the initial letter table is not used for procedures?
However, the initial two bytes of the block are still there, so some form of linking is employed in the storage of procedures. The procedure name is terminated by a zero byte.
Then we get what appears to be a 16 bit address. &E90 will probably be the address of the first byte of the proceudre. Judicious use of the indirection operator will confirm this.
Using the same methods as outlined previously, examining the starting pages of memory while calling procedures, it turns out that procedures have a dedicated address in the table at &482. The relevant address is &4F6. If you check, you will find that after the program has been run, the address contained is &EA6.
PROCHELLO HELLO PRINT ~!(&4F6 AND &FFFF) EA6
Next, I checked to see if functions were organised in the same way:
DELETE 2000,2020 10 PRINT FNHELLO 2000 DEF FNHELLO="HELLO" RUN HELLO E9C 20---> E9D 0 E9E 48--->H E9F 45--->E EA0 4C--->L EA1 4C--->L EA2 4F--->O EA3 0 EA4 92 EA5 E EA6 29--->) PRINT ~(!&4F8 AND &FFFF) E9C
As you can see, things are similar. It turns out that locations &4F8 is used as the function pointer. To recap, functions and procedures are linked together via their first two bytes, and the address &4F6 and &4F8. The block contains the name and the start address of the function/procedure. After doing all this, the final program looked like this:
10 PRINT FNHELLO 1000 @%=4 1010 FOR T%=TOP TO (!2 AND &FFFF) 1020 PRINT ~T%,~T%; 1030 A%=?T% MOD 128 1040 IF A%>31 THEN PRINT "--->";CHR$(A% ); 1050 PRINT 1060 NEXT T% 1070 END 2000 DEF FNHELLO="HELLO"
Using some of the information contained in this chapter, here is an application program to list out all the variables which are active when it is run.
10 REM Copyright (c) Jeremy Ruston 20 REM eg : 30 ZXC%=234 40 H=23.345 50 GFHJTRJ_SEG=PI 60 ASD$="A STRING" 70 D$="ANOTHER" 80 DIM R(10) 90 DIM RF(3,4) 100 DIM A%(23) 110 DIM WER%(1,3,2) 120 DIM K$(23) 130 DIM HJE$(2,4,1) 1000 REM ***************************** 1010 REM Variable list... 1020 REM Lists integer, real and 1030 REM string variables. 1035 REM (1777 bytes long !) 1040 REM ***************************** 1050 @%=0 1060 DIM E% 255 1070 FOR T%=&482 TO &4F4 STEP 2 1080 IF FNDD(T%)<>0 THEN PROCfollow(FND D(T%),(T%-&400)/2) 1090 NEXT T% 1100 END 1110 REM ***************************** 1120 DEF PROCfollow(T%,S%) 1130 $E%=CHR$(S%) 1140 R%=T%+1 1150 PRINT CHR$(S%); 1160 REPEAT 1170 R%=R%+1 1180 IF ?R%>64 THEN PRINT CHR$(?R%);:$E %=$E%+CHR$(?R%) 1190 UNTIL ?R%<64 1200 IF ?R%=&25 THEN PROCinteger 1210 IF ?R%=&24 THEN PROCstring 1220 IF ?R%=&00 THEN PROCreal 1230 IF ?R%=&28 THEN PROCreal_array 1240 IF FNDD(T%)>255 THEN PROCfollow(FN DD(T%),S%) 1250 ENDPROC 1260 REM ***************************** 1270 DEF PROCinteger 1280 IF R%?1=0 THEN PRINT "%=";EVAL($E% +"%"):ENDPROC 1290 PRINT "%("; 1300 FOR D%=1 TO ((R%?3)-1)/2 1310 IF D%<>1 THEN PRINT ","; 1320 PRINT FNDD(D%*2+R%+2)-1; 1330 NEXT D% 1340 PRINT ")" 1350 ENDPROC 1360 REM ***************************** 1370 DEF PROCstring 1380 IF R%?1=0 THEN PROCnormal_string E LSE PROCstring_array 1390 ENDPROC 1400 REM ***************************** 1410 DEF PROCnormal_string 1420 PRINT "$="; 1430 PRINT CHR$(34); 1440 IF R%?5=0 THEN PRINT CHR$(34):ENDP ROC 1450 FOR L%=1 TO R%?5 1460 PRINT CHR$(?(L%-1+FNDD(R%+2))); 1470 NEXT L% 1480 PRINT CHR$(34) 1490 ENDPROC 1500 REM ***************************** 1510 DEF PROCstring_array 1520 PRINT "$("; 1530 FOR D%=1 TO ((R%?3)-1)/2 1540 IF D%<>1 THEN PRINT ","; 1550 PRINT FNDD(D%*2+R%+2)-1; 1560 NEXT D% 1570 PRINT ")" 1580 ENDPROC 1590 REM ***************************** 1600 DEF PROCreal 1610 PRINT "=";EVAL($E%) 1620 ENDPROC 1630 REM ***************************** 1640 DEF PROCreal_array 1650 PRINT "("; 1660 FOR D%=1 TO ((R%?2)-1)/2 1670 IF D%<>1 THEN PRINT ","; 1680 PRINT FNDD(D%*2+R%+1)-1; 1690 NEXT D% 1700 PRINT ")" 1710 ENDPROC 1720 REM ***************************** 1730 DEF FNDD(A%)=!A% AND &FFFF RUN ASD$="A STRING" A%(23) D$="ANOTHER" GFHJTRJ_SEG=3.141592653 H=23.345 HJE$(2,4,1) K$(23) R(10) RF(3,4) WER%(1,3,2) ZXC%=234
The idea was for a routine which could be placed in an unused section of memory and then called whenever a record of variable contents was required during program development.
The program as presented here uses a set of dummy variables, lines 30 to 130 as a demonstration. To use the program, you would omit these lines, and situate it near the top of memory. Then, when you have run the program from which you wish to dump variables, return PAGE to the variable list program, and type RUN. Remember the CTRL-B if you want printer output.
If you RUN the program as it stands, you should notice a number of things about its output.
First, the variables are printed in alphabetical order of first letters. This may give you an idea of how the program operates.
Second, in the case of arrays, the computer just prints out the dimensions of the array, rather than wasting space with its contents.
Because the program must not upset any of the variables used by the original program, it uses integer variables throughout. This also means that the single string used must be created rather deviously.
1050 Sets the field width to zero. This is because of the need for the array dimensions to be printed next to each other.
1060 This line dimensions the string used in the program. It allows for variable names up to 255 characters long in this version. You may like to restrict the length to 30 or so!
1070 Starts a loop through all of the initial variable name letters io that table.
1080 If any variables exist starting with that letter, call PROCFOLLOW, which will follow the tree and print out the variables as it comes across them.
1090 Ends the loop through all the letters.
1100 Ends the program.
1120 Starts the definition of PROCFOLLOW. This procedure is called with the address of where it can find a variable, and the initial letter of the variable. It will carry on calling itself recursively until this address is less than 256, which indicates the end of the tree.
1130 $E% will hold the name of the variable, so it is started off with the letter PROCFOLLOW was called with.
1140 R% is used to point to the next letter of the name in the list. It is set to the second link byte here, to allow for variables which are only a single character long.
1150 PRINTS the first letter of the name.
1160 Starts a REPEAT loop, which will continue until all the letters of the name have been printed.
1170 Increments R%, to point to the next letter. You can now see why R% was first set to be a 'byte too low'.
1180 If the next letter is a legal one, prints it, and adds the letter to $E%.
1190 Ends the loop when an invalid letter came up.
1200 This line starts a section of code which calls various procedures, depending on the nature of the variable being processed. If the next character in the name was a percentage sign, for example, this line sends the program off to the integer variable handling procedure.
1210 If it was a dollar sign, the string procedure is called.
1220 If the next byte was the zero byte, there is no modifier on the end of the variable name, so it must be a real variable, so the REAL procedure is called.
1230 If the next character was a bracket, there is no symbol between the end of the name and the bracket, so it must be a real array.
1240 If the link bytes for this variable are legal, recursively calls PROCFOLLOW to follow the link bytes.
1250 Or else, end the procedure. If this procedure has been called a number of times all the ENDPROCs will fall through each other, so neatly ending the whole program.
1270 Starts the definition of PROCINTEGER. This proceudre processes integer variables and integer arrays.
1280 If the next byte after the percentage sign is zero, it is not an array, so prints out its contents, using EVAL in a way never intended by its designers, and exists.
1290 Otherwise, it must be an array, so print the opening bracket.
1300 Starts a loop through all the dimensions of the array. The last parameter of the FOR statement is a derivation of the 2*n+1 formula.
1310 If this is not the first dimension, prints the separating comma.
1320 Prints the number of elements in the current dimension.
1330 Ends the loop
1340 Prints the closing bracket.
1350 Ends PROCINTEGER
1370 Starts the definition of PROCSTRING
1380 If it is an array, call PROCSTRING_ARRAY, else call PROCNORMAL_STRING. The test is made by seeing if there is an opening bracket in the name of the variable.
1390 Ends PROCSTRING.
1410 Starts the definition of PROCNORMAL_STRING.
1420 Prints the equals sign, and subscripts the variable.
1430 Prints the opening quote of the contents of the string.
1440 If the string is null, prints the closing quote, and returns.
1450 For each of the characters in the string,
1460 Prints the character,
1470 Ends the loop.
1480 Prints the closing quote.
1490 Ends PROCNORMAL_STRING.
1510 Starts the definition of PROCSTRING_ARRAY
1520 Prints the subscript and the opening bracket of the array.
1530 Starts a loop through all of the dimensions of the string array.
1540 If this is not the first dimension, prints the separating comma.
1550 Prints the number of elements in the current dimension.
1560 Ends the loop
1570 Prints the closing bracket of the array.
1580 Ends PROCSTRING_ARRAY
1600 Starts the definition of PROCREAL.
1610 Prints the equals sign, and the value of the variable.
1620 Ends PROCREAL.
1640 Starts the definition of PROCREAL_ARRAY.
1650 This section of code is almost identical to that in lines 1520 to 1570.
1730 Defines a double byte interrogation function.
For easy reference, this table lists the locations of all the single letter integer variables:
@% is stored at &400 A% is stored at &404 B% is stored at &408 C% is stored at &40C D% is stored at &410 E% is stored at &414 F% is stored at &418 G% is stored at &41C H% is stored at &420 I% is stored at &424 J% is stored at &428 K% is stored at &42C L% is stored at &430 M% is stored at &434 N% is stored at &438 O% is stored at &43C P% is stored at &440 Q% is stored at &444 R% is stored at &448 S% is stored at &44C T% is stored at &450 U% is stored at &454 V% is stored at &458 W% is stored at &45C X% is stored at &460 Y% is stored at &464 Z% is stored at &468
And similarly, here is a table of locations for the first letter of other variables:
'A'---> &482 'K'---> &496 'B'---> &484 'L'---> &498 'C'---> &488 'M'---> &49A 'D'---> &48A 'N'---> &49C 'E'---> &48A 'O'---> &49E 'F'---> &48C 'P'---> &4A0 'G'---> &48E 'Q'---> &4A4 'H'---> &490 'S'---> &4A6 'I'---> &492 'T'---> &4A8 'U'---> &4AA 'h'---> &4D0 'V'---> &4AC 'i'---> &4D2 'W'---> &4AE 'j'---> &4D6 'X'---> &4B0 'k'---> &4D8 'Y'---> &4B2 'l'---> &4DA 'Z'---> &4B4 'm'---> &4DC '['---> &4B6 'n'---> &4DC '\'---> &4B8 'o'---> &4DE ']'---> &4BA 'p'---> &4E0 '^'---> &4BC 'q'---> &4E2 '_'---> &4BE 'r'---> &4E4 '`'---> &4C0 's'---> &4E6 'a'---> &4C2 't'---> &4E8 'b'---> &4C4 'u'---> &4EA 'c'---> &4C6 'v'---> &4EC 'd'---> &4C8 'w'---> &4EE 'e'---> &4CA 'x'---> &4F0 'f'---> &4CC 'y'---> &4F2 'g'---> &4CE 'z'---> &4F4
This program is not the most useful you'll find in this book:
10 REM ?????????????????????? 20 LET A=PI 30 FOR T%=&484 TO &4F4 STEP 2 40 ?T%=?&482 50 T%?1=?&483 60 NEXT T% RUN PRINT Q 3.14159265 PRINT W 3.14159265 PRINT R 3.14159265 PRINT JK 3.14159265 PRINT I 3.14159265 PRINT P 3.14159265 PRINT B 3.14159265 PRINT M 3.14159265 PRINT V 3.14159265 PRINT X 3.14159265 PRINT F 3.14159265 PRINT H 3.14159265 PRINT Y 3.14159265 PRINT HII No such variable REM Etc, etc...
It creates a variable 'A' and then directs all the other variable pointers to the same variable. The net effect is that every variable you later create will be treated as if it started with the letter 'A'. This is demonstrated after the listing by printing a whole lot of single character variables, and amazingly, they are all the same as 'A'!
Can you think of any more useful applications for having two routes or more to a single variable?
I thought not, but later I was faced with passing an array to a procedure (the same technique is applicable to user definable functions). The solution I cam up with is demonstrated in this program:
10 REM ***************************** 20 REM Passing arrays to procedures. 30 REM Copyright (C) Jeremy Ruston 40 REM ***************************** 50 DIM N$(2),M$(2) 60 N$(1)="LED" 70 N$(2)="ZEPPELIN" 80 M$(1)="ARE" 90 M$(2)="GREAT" 100 PROCexample("N$") 110 PROCexample("M$") 120 END 130 REM ***************************** 1000 DEF FNfind(A$) 1010 LOCAL B$,ad,add,first 1020 first=ASC(A$) 1030 ad=!(first*2+&400) AND &FFFF 1040 REPEAT 1050 IF ad<255 THEN PRINT '"No such arr ay at PROCfind":END 1060 add=ad 1070 B$="" 1080 REPEAT 1090 ad=ad+1 1100 B$=B$+CHR$(ad?1) 1110 UNTIL ad?1=0 1120 ad=!add AND &FFFF 1130 UNTIL B$=MID$(A$,2)+"("+CHR$(0) 1140=add 1150 REM ***************************** 2000 DEF PROCexample(array_name$) 2010 LOCAL 2020 add=FNfind(array_name$) 2030 ?&4BE=add MOD 256 2040 ?&4BF=add DIV 256 2050 FOR T=1 TO 2 2060 PRINT _$(T) 2070 NEXT T 2080 ENDPROC 2090 REM ***************************** RUN LED ZEPPELIN ARE GREAT
The only general part of the program is PROCfind(A$). This procedure finds the address of the first link byte of the array of name A$. A$ should not contain the opening bracket of the name, but it should contain the modifier ($ or %).
PROCfind is based on some of the routines in the variable program, so it should not need a detailed explanation.
The routine is based on the assumption that you will not have any arrays in your program called '_' (underscore). If you do, the array will be lost.
Having entered PROCfind, all you have to do to include an array in the parameters of your functions or procedures is:
ii) Then use PROCfind to work out the start address of the array of this name (line 2020).
iii) Then substitute this address for the pointer for a character not often used to start variable names, such as the underscore character.
iv) Then use the underscore character, or whatever you chose, as the name of the array you used as the parameter. Remember to put the correct modifiers after it.
This program is a derivation of the movement program I gave in chapter one.
If you have the movement program on cassette, it would be quicker to modify that than to type this whole program in.
10 REM Copyright (C) Jeremy Ruston 20 REM MCCMLXXXII 30 MODE 1 40 VDU 19,3,4,0,0,0 50 VDU 5 60 PROCASSEMBLE 70 START%=HIMEM/8 80 X%=0 90 Y%=0 100 B%=20 110 A%=1 120 REPEAT 130 B%=B%-1 140 IF B%=0 THEN B%=RND(20)+10:A%=RND( 64)-1:GCOL 0,RND(3) 150 IF A% AND 1 THEN Y%=(Y%+31) MOD 32 160 IF A% AND 2 THEN Y%=(Y%+1) MOD 32 170 IF A% AND 4 THEN X%=(X%+1) MOD 80 180 IF A% AND 8 THEN X%=(X%+79) MOD 80 190 IF A% AND 16 THEN X%=(X%+1) MOD 80 200 IF A% AND 32 THEN X%=(X%+79) MOD 8 0 210 S%=START%+X%+Y%*80 220 ?&D00=S% DIV 256 230 ?&D01=S% MOD 2566 240 CALL &D10 250 R%=S%*8 260 ?&322=R% MOD 256 270 ?&323=R% DIV 256 280 VDU 30,42 290 UNTIL FALSE 300 DEF PROCASSEMBLE 310 P%=&D10 320[OPT 0 330 LDA #12:STA &FE00 340 LDA &D00:STA &FE01 350 LDA #13:STA &FE00 360 LDA &D01 STA &FE01 370 RTS:] 380 ENDPROC