Spectrum MC/Basic Sorting Variables If you find that setting the parameters of a machine-code routine is a bit of a drag, Stewart Nicholls' program could give you the best of both worlds. Do you get tired of having to set parameters of a machine- code routine by poking numerous addresses with the neces- sary values? If you do, the following machine-code routine is the answer: it makes poking completely unnecessary. The problem is avoided by setting up the values using Basic variables and then searching the variables area to find their value and storing them in the spare bytes of the printer buffer. To demonstrate the technique, I have attached the search program to a routine which prints a message anywhere on the screen using characters of any height or width, in any ink colour. The x and y variables are used for the start of the mes- sage; since these are the co-ordinates of the top-left corner of the first character of the message the usual Spectrum plot positions apply. That is, 0,0 is the bottom left-hand corner of the screen. For the height of the cha- racters, h is used. Normal height is 1, 22 will be full screen. Width of characters is 2. Normal width is 1, 32 is full screen for one character. The string containing the message to be printed will be a$. The only limitations on the message are that it can only contain characters from code 32 - space - to code 127 - copyright - and that the number of characters multiplied by width of character should not exceed 23 with the plot position at the left-hand edge of screen. If this is ex- ceeded the program will not crash but the message will wrap round the screen overprinting as it goes. The program is fully mug-trapped and any wrong parameter of x and y will be corrected before plotting the string. The machine code to find the variables x, y, h, w and a$ is shown in listing 1. [Listing 1 was a code loader, "large" on the TZX. This loaded (and tested) the machine code de- scribed, which is on the TZX as "largecode".] If you read through Chapter 24 of the Sinclair Manual, you will see that there are six types of variable, namely: a number whose name is one letter - type (i); a number whose name is two or more letters - type (ii); an array of numbers - type (iii); FOR-NEXT loop control - type (iv); string variable - type (v); an array of characters - type (vi). Each of these variables is uniquely identified by its first byte. Variable type i = 96d + (letter code - 96d) Variable type ii = 160d + (first letter code - 96d) Variable type iii = 128d + (letter code - 96d) Variable type iv = 224d + (letter code - 96d) Variable type v = 64d + (letter code - 96d) Variable type vi = 192d + (letter code - 96d) The variables we will be searching for will be types (i) and (v). If we take, for example, the variable x which has a cha- racter code of 120, then using the formula (i) given, its unique first byte is 120. So in order to find variable x we must search through the variables for this unique code, which will then point to the start of the x variable infor- mation. This is not just a simple case of running through the variables one byte at a time using a CPIR instruction, as this may find the code 120 held in a string or an array of characters. So we must find the start of each variable, check if it is the one we want, and if not, then jump to the start of the next variable. This means that we must identify the type of variable found before we know how far to jump to the next one. This is not as difficult a task as it might first appear because of the way in which the Spectrum stores the variable parameters. With three of the variables, namely array of numbers, array of character and strings, the length of the variable is held in the two bytes following the unique byte code. What is more helpful, bit 5 of the unique code is zero and the remaining three variables have bit 5 set at one. The length of the three remaining variables can be calcu- lated by this method: Type (i) length = 6 bytes including unique code. Type (ii) length = number of letters of variable + 5 bytes. Type (iv) length = 19 bytes including unique code. We now have the information to enable us to jump over each type of variable, and the checks must be made in the follo- wing order: first, check if the code is 120; if yes then return from routine; check bit 5 and if zero jump by value in next two bytes + 1 : GOTO 1 Check bit 6 and if zero check following bytes for BIT 7 = 1 that is the last character of the variable, then jump six bytes and Goto 1. Check but 7 and if zero then jump 6 bytes and Goto 1. Now it must be a FOR/NEXT loop so jump 19 bytes and Goto 1. The mnemonics of the machine-code to do this are shown in the Find subroutine. This uses address 23728 to hold the unique code of the variable for which we are searching. The HL register holds the address being checked, and the accu- mulator is loaded with the unique code from address 23728. The subroutine only takes 46 bytes to check, jump and locate the start address of any variable. Now that we have found the start address of our variable x we must find its value. You will see that the value for whole numbers is held in the third and fourth bytes follo- wing the unique code. In our case we are only interested in numbers from 0 to 255 for x. So we can ignore the high byte and store the low byte, that is the third byte. This applies to all our variables x, y, h and w. With this in mind a subroutine, Setup, can be assembled to move along three bytes from the address held in HL and then store the value held in this address in the printer buffer area of memory. So we now have the means of finding and storing values of x, y, h and w in addresses 23296/7/8 and 9. We can now move to the slightly more complicated string parameters. In our case we need to find a$, unique code 65. The same Find routine can be used to find the start of the variable. Once found we then move to the next two bytes to find the LEN of the string as follows: INC HL LD E,(HL) INC HL LD D,(HL) This will put the length of the string into the DE regis- ter, and, as we can safely limit the length of the string to 255 characters we only need to store the number in low byte: LD (23300),DE The number in address 23301 will be overwritten with the first character in the string: PUSH DE Get number of characters into BC POP BC INC HL Set HL at start of characters LD DE,23301 LDIR Transfer information The above will transfer the string characters from the variables to the printer buffer starting at address 23301. With this routine we now have x, y, h, w, length of string, and characters in string stored consecutively in the printer buffer, and can go straight into the routine to plot the string based on these parameters. The plot routine finds the start of the eight bytes for each character in the character and, for each byte, a Rotate Left instruction is carried out either plotting or unplotting depending whether Carry is set or not. Again use is made of the two unused bytes in the Systems Variables 23728/9 to hold and update the x,y plot positions for each character. If the y plot position goes below zero, then it is reset to 175 to give a wrap around effect and likewise if the x position goes above 255 it is reset to zero. So now we have a machine-code program than can be called from Basic with no POKEs in sight - for example: 10 LET x=0: LET y=0: LET h=8: LET w=4: LET a$="Finished": INK 6: RANDOMIZE USR 32393 This saves 13 POKEs including LEN a$. Listing 1 ["large"] shows the Basic program to set up the machine code above RAMTOP. On a 16K Spectrum this is immediately before the user-defined graphics and occupies 255 bytes. Once the machine-code is entered the Basic can be NEWed and the code saved in the usual way. There are a couple of points to watch with this program. First, ensude that all the variables are defined before a call is made to the routine as failure to find a variable will crash the program. Second, do not use x, y, h or w as a control variable in a FOR-NEXT loop because the Spectrum will then delete the simple variable and use the FOR-NEXT loop to hold further values of x, y, h and w. This can be demonstrated by the simple Basic program: 10 FOR a=1 TO 10 20 PRINT a; 30 NEXT a 40 LET a=3 50 NEXT a This program will produce a continuous loop resetting a to 3 and jumping back into the FOR-NEXT loop showing that the variable - a - is line 40 is part of the FOR-NEXT loop control variable. If this was not the case the Spectrum would give an error report: 1 NEXT without FOR, 50:1 You can have a small Basic subroutine to work out the width of characters and the start plot position to give information printed centrally on any line; such a program is given in listing 2 with sample printout. [This is called "Plot" on the TZX, with one line added to auto-load the machine code.] We can now tackle a machine-code program to print out all the variables used after a program has been run. Note that this may not be all the variables in the listing as when a program is run certain subroutines may not have been called and so variables held in those routines will not have been placed into the variables area. This program - listing 3 - could be of use in debugging Basic programs. Listing 3 is in the form of a machine-code dump which should be held in DATA statements as listing 1, and poked into a memory position of your choice. There are no Jump or Call commands to within the program. I find it handy to have two versions of the program, one stored above RAMTOP and another in a line 1 REM statement containing 331 zeros and called using RANDOMIZE USR 23760 The machine-code is then held in the Basic area of RAM and can be merged with any program - as long as the program does not have a line 1, which will be overwritten. One point to note when saving the Basic REM statement on tape is to use CLEAR to erase all variables used in the machine- code loader, otherwise these will be saved and merged along with the REM statement. This is good practice on any pro- gram which does not require the variables to be saved. [This code, saved as a CODE file ready to be loaded any- where in memory, including into a REM statement should you want to, is on the TZX as "variables".] Listing 4 is a demonstration program setting up varia- bles, and shows a screen copy of the machine-code output which lists these variables under their variable types. Notice how the Spectrum converts all variables to lower case and also that variables a, h, l, o, w, x and z have been listed under the FOR-NEXT loop only and not under number. [This is on the TZX as "Basicvars".] Listing 5 shows the machine-code mnemonics for assembly into a REM statement, for anyone who wishes to check through the program to see how it works. The first section from addresses 5CD0 to 5CE5 sets up the border, paper and ink colours; from 5Ce6 to 5D46 prints the heading. The remainder of the program checks the variables one by one finding the type of variable, locating the next print posi- tion in that variables column, updating the print position and printing the variable. The section from 5D94 to 5DBE is interesting in that it checks for a column reaching the bottom of the screen and if it does then waits for a key press before scrolling 21 lines, leaving the heading on the screen and printing the next variable in position. A return to Basic is only made when all the variables have been listed. [ As listing 5 provided nothing more than a bare assembly dump of the code (with a typo, to boot), I haven't copied it. Listings 1 and 2, however, were accompanied by (unnum- bered) hex listings with labels (which are referred to in the article). The order was not quite rational, but sorted by address, these listings were as follows: ] Start address for 16K machine code 32235 ORG 32235 Subroutine to find variables FIND LD HL, (23627) START: LD A, (23728) CP (HL) RET Z BIT 5, (HL) JR NZ, NEXT INC HL LD E, (HL) INC HL LD D, (HL) ADD HL, DE INC HL JR START NEXT: BIT 6, (HL) JR NZ, NEXT1 LOOP5: INC HL LD A, (HL) BIT 7, A JR Z, START LOOP6: LD DE, 6 ADD HL, DE JR START NEXT1: BIT 7, (HL) JR Z, LOOP6 LD DE, 19 ADD HL, DE JR START Subroutine to set parameters of variable to be found SETUP LD (23728), A CALL FIND INC HL INC HL INC HL LD A, (HL) LD (BC), A RET Start of machine-code routine RUN LD BC, 23296 LD A, 120 CALL SETUP INC BC LD A, 121 CALL SETUP INC BC LD A, 104 CALL SETUP INC BC LD A, 119 CALL SETUP LD A, 65 LD (23728), A CALL FIND INC HL LD E, (HL) INC HL LD D, (HL) LD (23300), DE PUSH DE POP BC INC HL LD DE, 23301 LDIR Routine to plot the string LD HL, (23296) XOR A LD A, H SBC A, 176 JR C, YES LD H, A LD (23296), HL YES: LD (23728), HL LD HL, 23301 RUN1: PUSH HL LD A, (HL) LD H, 0 LD L, A ADD HL, HL ADD HL, HL ADD HL, HL LD DE, 15360 ADD HL, DE LD B, 8 LOOP4: PUSH BC LD BC, (23297) LOOP3: LD A, (HL) PUSH HL PUSH BC LD B, 8 LOOP2: PUSH BC RLA PUSH AF JP C, PLOT LD HL, (23299) LD A, (23728) ADD A, L LD (23728), A JP 32527 PLOT: LD BC, (23298) LOOP1: PUSH BC LD BC, (23728) PUSH BC CALL 22E5H POP BC INC C LD (23728), BC POP BC DJNZ LOOP1 END: POP AF POP BC DJNZ LOOP2 LD A, (23296) LD HL, 23728 LD (HL), A INC HL XOR A LD A, (HL) SBC A, 176 JR C, OK LD (HL), A JR CONT OK: LD A, (HL) CP 0 JR NZ, OK1 LD (HL), 176 OK1: DEC (HL) CONT: POP BC POP HL DJNZ LOOP3 INC HL POP BC DJNZ LOOP4 LD A, (23299) ADD A, A ADD A, A ADD A, A LD L, A LD A, (23728) ADD A, L LD (23296), A LD (23728), A LD A, (23297) LD (23729), A POP HL INC HL LD A, (23300) DEC A RET Z LD (23300), A JP RUN1