What if the machine code needs different or more parameters?
Lets write a, slightly, more ambitious program, a program that reverses the order of a string. It would change "ABCDE" to "EDCBA". We can't pass a string to the machine code program as a parameter, only integers. Instead of passing the string value, we can pass the string address to the program. The program then knows exactly where to find the string value, and also where it can put the result. To reverse the string A$, the code is given the parameter ADDR(A$). The program will swap the last character with the first, the next to last with the second and so on, until the middle of the string is reached.
The machine code could look like this:
18 | XGDX | 'Put ADDR(A$) in the X register |
E600 | LDAB 0,X | 'Put length of A$ in B |
08 | INX | 'Put the address of the first string character in memory addresses 41 and 42. |
DF41 | STX 41 | 'The bytes at the addresses 41 to 4C can be freely used by machine code programs. Unfortunately the value of these is not always conserved in OPL so they can't be used to pass information between OPL and MC reliably. |
3A | ABX | '\Get address of last character in string by adding the |
09 | DEX | '/length minus 1. |
54 | LSRB | 'Divide the length by two. Is number of swaps necessary. |
2715 | BEQ end | 'If string is empty, or of length one, then return. |
lp: | ||
37 | PSHB | 'Temporarily store B, the number of swaps still needed. |
A600 | LDAA 0,X | 'Get character from end. |
3C | PSHX | 'Save position from end. |
DE41 | LDX 41 | 'Get position from beginning. |
E600 | LDAB 0,X | 'Get character from beginning. |
A700 | STAA 0,X | 'Store end character here. |
08 | INX | '\Move position further forward to centre. |
DF41 | STX 41 | '/and store it again. |
38 | PULX | 'Get position from end. |
E700 | STAB 0,X | 'Put beginning character here. |
09 | DEX | 'Move further back to the centre. |
33 | PULB | 'Get number of swaps still needed |
5A | DECB | 'Decrease by 1, since just did a swap. |
26EB | BNE lp | 'Loop until no swaps left to do |
end: | ||
39 | RTS | 'return |
Here is its implementation in OPL.
REVERSE: LOCAL MC$(33),A$(20) MC$=CONV$:("18E60008DF413A0954271537A6003CDE41") MC$=MC$+CONV$:("E600A70008DF4138E70009335A26EB39") DO INPUT A$ USR(ADDR(MC$)+1,ADDR(A$)) PRINT A$ UNTIL GET=1 OR A$=""
This machine code routine will reverse any ordinary string variable.
If you wish to use it on one element of a string array, you have to calculate the memory
address where that particular string starts because ADDR(A$()) returns the address of the
first of the strings and ADDR(A$(3)) for example doesn't work. In particular, if you have
declared A$() by LOCAL A$(10,20), then to reverse A$(X%) you will have to use
USR(ADDR(MC$)+1,ADDR(A$())+21*(X%-1)) since we have to skip over X%-1 strings
which each use 21 bytes of space. Look up how a string array is stored in
part 1 if this is not clear to you.
As we have seen, the USR function only allows us to pass one integer value to the machine code program. How can we pass more information along, more than just one integer or string?
There are essentially two methods. One is to find some addresses in memory and simply POKE values there. For this you need to know exactly which parts of the memory are used and which parts are safe to change. The second, and better option, is to put all the information in an array, an simply pass along the address of that array.
Lets illustrate this with another program. Suppose we want to have a
program that searches through a string variable, and replaces every
occurrence of one character with another. The machine code program needs
three pieces of information:
-The address of the string to be searched through.
-The character to be replaced.
-The character to replace it with.
These 3 things are put in an array, say A%().
A%(1)=ADDR(A$) A%(2)=ASC("%") A%(3)=63
The machine code program now only needs to know ADDR(A%()) to be able to look up all the information it needs.
The machine code could look like this:
18 | XGDX | 'Put the address of A%() in X |
E605 | LDAB 5,X | '\ Put the replacement character A%(3) in memory |
D741 | STAB 41 | '/ address 41. This makes looking it up easy later on. |
A603 | LDAA 3,X | 'Put character A%(2) in register A. |
A705 | STAA 5,X | '\ Swap values of A%(2) and A%(3). This way, the |
E703 | STAB 3,X | '/ routine will undo the changes if it is called again. |
'Remove these two lines if this is not wanted. | ||
EE00 | LDX 0,X | 'Put address of string in X. |
7F0042 | CLR 42 | 'Clear address 42. Used to count number of changes made. |
E600 | LDAB 0,X | 'Get current length of the string. |
2711 | BEQ end | 'If empty string, then return. |
lp: | ||
08 | INX | 'Point to the next character in the string. |
A100 | CMPA 0,X | 'Must it be changed? |
2609 | BNE skp | 'If not the skip the next part. |
36 | PSHA | 'Temporarily save value of A (=chr to search for) |
9641 | LDAA 41 | '\Get replacement character and store it in |
A700 | STAA 0,X | '/the string. |
7C0042 | INC 42 | 'Increase the counter. |
32 | PULA | 'Retrieve value of A. |
skp: | ||
5A | DECB | 'Decrease B, the number of character left to go. |
26EF | BNE lp | 'Loop until none left. |
end: | ||
D642 | LDAB 42 | '\ |
4F | CLRA | ' >Put counter value in X |
18 | XGDX | '/ |
39 | RTS | 'return this value to OPL. |
Now lets use this in an OPL procedure.
CHANGE: LOCAL MC$(38),A%(3),B% LOCAL A$(20) MC$=CONV$:("18E605D741A603"+"A705E703"+"EE007F0042E600271108A100") MC$=MC$+CONV$:("2609369641A7007C0042325A26EFD6424F1839") A%(1)=ADDR(A$) A%(2)=%* A%(3)=%+ INPUT A$ DO AT 1,1 PRINT A$ B%=USR(ADDR(MC$)+1,ADDR(A%())) UNTIL KEY PRINT B% GET
This program will change every "*" by
"+", then every "+" by "*" and repeat this till a key is
pressed. The number of changes made, is then shown. Try some other values of A%(2) and
A%(3). If A%(2) and A%(3) are the same ASCII code, then no changes are visible, but the
program will still return how often that character occurs.
The two lines in the MC program that swap A%(2) and A%(3) have been separated in the list
if hex code, so they can easily be removed if you wish. There are no relative jumps
crossing those two instructions so no other parts of the code need to be changed if you do this.
To use this routine on an element of a string array we have to use the same trick as with
the REVERSE routine.
These programs also show how you can pass any information from machine code back to OPL. The first program returned a string, by changing the value of a string variable directly. The second program changed the values of two integer variables. If an MC program needs to return a lot of information, just give it the address of a variable or array to store that information in.
In Part 3 of this series I will cover... A complete game using machine code!