MACHINE CODE CALCULATOR
part 4 of 5
by Toni Baker
from ZX Computing, October 1986

More explorations of the Spectrum's calculator functions in
Part 4 of Toni Baker's series.

[See the letter in the February 1987 issue from Steven Gunhouse giving BASIC  ]
[equivalents of the functions FN f$(x$,x) and FN f(n) using recursive DEF FNs.]
[(Although there were so many printing errors in the BASIC that the commands  ]
[are useless as printed in the magazine. Working versions are given in the    ]
[files RECURSE.TXT and MCCALC.TAP "Recursive".)                           JimG]


There are many questions still unanswered. How do you get random
numbers (the RND function)? How can you calculate SCREEN$(X,Y)?. How
do you read the value of a BASIC variable? How do you slice a string?
These questions, and many more, will now be answered.

POINT: The machine code instruction CALL POINT_SUB (address 22CB) will
pop two numbers, X,Y, from the calculator stack, evaluate POINT(X,Y),
and will put the result back onto the calculator stack.

ATTR: The machine code subroutine S_ATTR_S at address 2307 will take
two numbers, Y,X, from the calculator stack, and replace them with
ATTR(Y,X).

SCREEN$: The machine code subroutine S_SCRN$_S at address 2535 will
take two numbers, Y,X, from the calculator stack, and replace them
with the string SCREEN$(Y,X). The SCREEN$ bug which is present in
BASIC is fortunately not present in this machine code routine, so you
can happily use it with no problems. (In BASIC, the expression
"*"+SCREEN$(0,0) will almost invariably give the wrong answer - try it
by printing various different things at position 0,0!).

The SCREEN$ function will only detect ASCII characters. It will not
detect either block graphics or user defined graphics. By altering the
value of the system variable (CHARS) to (UDG)-100h prior to calling
S_SCRN$_S it is possible to search for the user defined graphics
instead of the ASCII characters. You must restore (CHARS) after the
subroutine call. The value returned by the subroutine will have
character code 70h less than the UDG found.

More comprehensively, the program which was listed in last month's ZX
Computing in the article "Elementary Graphics Part Three" will detect
and correctly return all characters.

SLICING: Figure One lists a program which I have called SLICE. Its
purpose is to slice strings, in the same way that you can in BASIC. To
slice a string simply CALL SLICE (which must of course be written into
the Spectrum's memory at some convenient location) and the job will be done.

The subroutine will remove three items from the calculator stack,
A$,X,Y and will replace them with the sliced string A$(X TO Y). Also
in Figure One is a program called SLICE_INT which only requires one
item (the string to be sliced), A$ say, on the calculator stack, and
will replace it with the sliced string A$(BC TO DE).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 1
EA60 CD991E   SLICE     CALL FIND_INT2     ;BC= second slice parameter
EA63 C5                 PUSH BC            ;Store on machine stack
EA64 CD991E             CALL FIND_INT2     ;BC= first slice parameter
EA67 C5                 PUSH BC            ;Temporarily store on stack
EA68 CDF12B             CALL STK_FETCH     ;DE= address of string to slice
EA6B AF                 XOR  A             ;A= zero
EA6C EB                 EX   DE,HL         ;HL= address of string
EA6D D1                 POP  DE            ;DE= first slice parameter
EA6E C3962A             JP   SL_DEFINE_2   ;Jump into ROM routine to
                                           ;complete the task

EA71 D5       SLICE_INT PUSH DE            ;Stack second parameter
EA72 C5                 PUSH BC            ;Temporarily stack first parameter
EA73 CDF12B             CALL STK_FETCH     ;DE= address of string
EA76 AF                 XOR  A             ;A= zero
EA77 EB                 EX   DE,HL         ;HL= address of string
EA78 D1                 POP  DE            ;DE= first parameter
EA79 C3962A             JP   SL_DEFINE_2   ;Slice the string
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

RND: The routine labelled RND in Figure Two will evaluate the RND
function, leaving a random number between 0 and 1 as a new entry at
the top of the calculator stack. There is no real need to write the
routine as a subroutine, since it is so short you might just as well
include it as it stands in your programs.

As with the BASIC RND function it suffers from the disadvantage that
it is incredibly slow. For this reason Figure Two contains two other
programs, labelled RANDOM and RNDQ respectively. The purpose of RANDOM
is to assign (SEED) with the next value in the pseudo- random
sequence. On return HL will contain the new value of (SEED), being a
random word between 0000 and FFFF; also, the A register will contain
the high part of this number, and will therefore be a random byte
between 00 and FF.

The RNDQ subroutine (which relies on the existence of the RANDOM
subroutine) will do exactly the same job as RND - ie. will leave a
random number between 0 and 1 at the top of the calculator stack - but
will do it much, much faster.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 2
EA7C CD98EA   RNDQ      CALL RANDOM        ;Find new value for (SEED)
EA7F 44                 LD   B,H
EA80 4D                 LD   C,L           ;BC= random word
EA81 CD2B2D             CALL STACK_BC      ;Place on calculator stack
EA84 EF                 RST  #28           ;Switch on calculator
EA85 3D                 restack            ;Re-stack in floating point form
EA86 38                 end_calc           ;Switch off calculator
EA87 7E                 LD   A,(HL)        ;A= exponent byte
EA88 A7                 AND  A
EA89 C8                 RET  Z             ;Return if random number is zero
EA8A D610               SUB  #10
EA8C 77                 LD   (HL),A        ;Reduce exponent by 16, thus
EA8D C9                 RET                ;dividing number by 65536

EA8E 061D     RND       LD   B,#1D         ;B= calculator code for VAL
EA90 EF                 RST  #28           ;Switch on calculator
EA91 34B03F0201         stk_data "RND"     ;Stack the string for "RND"
     3B                 execute B          ;Evaluate the string using VAL
     38                 end_calc           ;Switch off calculator

EA98 2A765C   RANDOM    LD   HL,(SEED)     ;HL= previous random number seed
EA9B 7C                 LD   A,H
EA9C A5                 AND  L
EA9D 3C                 INC  A
EA9E 2005               JR   NZ,RANDOM_2   ;Jump unless seed = FFFF
EAA0 21B5FF             LD   HL,#FFB5      ;In which case new value is FFB5
EAA3 182A               JR   RAND_EXIT
EAA5 AF       RANDOM_2  XOR  A             ;AHL= SEED
EAA6 23                 INC  HL            ;AHL= SEED+1
EAA7 54                 LD   D,H
EAA8 5D                 LD   E,L           ;DE= SEED+1
EAA9 29                 ADD  HL,HL
EAAA 8F                 ADC  A,A           ;AHL= 2*(SEED+1)
EAAB 29                 ADD  HL,HL
EAAC 8F                 ADC  A,A           ;AHL= 4*(SEED+1)
EAAD 29                 ADD  HL,HL
EAAE 8F                 ADC  A,A           ;AHL= 8*(SEED+1)
EAAF 19                 ADD  HL,DE
EAB0 CE00               ADC  A,#00         ;AHL= 9*(SEED+1)
EAB2 29                 ADD  HL,HL
EAB3 8F                 ADC  A,A           ;AHL= 18*(SEED+1)
EAB4 29                 ADD  HL,HL
EAB5 8F                 ADC  A,A           ;AHL= 36*(SEED+1)
EAB6 19                 ADD  HL,DE
EAB7 CE00               ADC  A,#00         ;AHL= 37*(SEED+1)
EAB9 29                 ADD  HL,HL
EABA 8F                 ADC  A,A           ;AHL= 74*(SEED+1)
EABB 19                 ADD  HL,DE
EABC CE00               ADC  A,#00         ;AHL= 75*(SEED+1)
EABE 24                 INC  H
EABF 25                 DEC  H
EAC0 2006               JR   NZ,RAND_MOD   ;Jump unless HL<0100
EAC2 BD                 CP   L
EAC3 2803               JR   Z,RAND_MOD    ;Jump if A=HL
EAC5 3801               JR   C,RAND_MOD    ;Jump if A<HL
EAC7 3D                 DEC  A             ;Adjust for special case
EAC8 5F       RAND_MOD  LD   E,A
EAC9 1600               LD   D,#00
EACB A7                 AND  A
EACC ED52               SBC  HL,DE         ;HL= remainder modulo 65537
                                                of 75*(SEED+1)
EACE 2B                 DEC  HL            ;HL= (75*(SEED+1) MOD 65537)-1
EACF 7C       RAND_EXIT LD   A,H           ;A= random byte
EAD0 22765C             LD   (SEED),HL     ;Store new value for system variable
EAD3 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

PI: Since the calculator has a built in procedure for calculating
PI/2, the calculation of PI is particularly easy. Figure Three
contains four different routines for calculating PI - use whichever of
the three [four] you want. They are listed in order of speed - the
first is the slowest.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 3
EAD4 061D     PI_VERSN1 LD   B,#1D         ;B= calculator code for VAL
EAD6 EF                 RST  #28           ;Switch on calculator
EAD7 34B0380201         stk_data "PI"      ;Stack the string "PI"
     3B                 execute B          ;Evaluate the string using VAL
     38                 end_calc           ;Switch off calculator

EADE EF       PI_VERSN2 RST  #28           ;Switch on calculator
EADF A3                 const PI/2         ;Stack half of PI
EAE0 31                 duplicate
     0F                 add                ;Double it
     38                 end_calc           ;Switch off calculator

EAE3 EF       PI_VERSN3 RST  #28           ;Switch on calculator
EAE4 A3                 const PI/2         ;Stack half of PI
EAE5 38                 end_calc           ;Switch off calculator
EAE6 34                 INC  (HL)          ;Increment exponent byte, thus
                                           ;doubling the number

EAE7 EF       PI_VERSN4 RST  #28           ;Switch on calculator
EAE8 34F2490FDAA2       stk_data 3.1415927 ;Stack constant PI directly
     38                 end_calc           ;Switch off calculator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

INKEY$: INKEY$ (as opposed to INKEY$ #N) is particularly easy to do in
machine code. The program listed in Figure Four will do the job
nicely, placing the string result of an immediate keyboard scan at the
top of the calculator stack. INKEY$ #N, of course, needs no special
routine to do that.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 4
EAEF 0618     INKEY$    LD   B,#18         ;B= calculator code for VAL$
EAF1 EF                 RST  #28           ;Switch on calculator
EAF2 34B0390201         stk_data "INKEY$"  ;Stack the string "INKEY$"
     3B                 execute B          ;Evaluate the string using VAL$
     38                 end_calc           ;Switch off calculator
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Communicating with BASIC

Using machine code throughout is good practice, but sometimes it makes
things easier if you can use numbers and strings on the calculator
which were specified in BASIC. Better still if you could use machine
code results in BASIC. There are a number of ways to do this so let's
look at the easy ways first.

Consider the following BASIC statement:
	LET P=Q+USR address
This will call a machine code subroutine at the specified address, but
with a rather interesting property. At the time the machine code is
executed the parameter Q will be at the top of the calculator stack.
This is really the easiest way of passing a numeric parameter from
BASIC to machine code. I'll give you an example - a little routine
which works out SQR (Q+1). In other words, you will be able to use LET
P=Q+USR address in place of LET P=SQR(Q+1). You will notice that this
technique firstly accepts a numeric parameter passed from BASIC, and
finally passes a numeric parameter calculated in machine code back to BASIC.

The example I mentioned is listed in Figure Five. The calculation of
SQR(Q+1) is really quite simple - since it is the parameter passing
which is the point of the example. There are some things to watch out
for: LET P=Q+R+USR address will pass Q+R to machine code. LET
P=(expression)+USR address will pass the value of the expression to
machine code. LET P=USR address+Q will not work!

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 5
EAF9 EF       DEMO_1    RST  #28           ;Switch on calculator.
                                           ;Note that Q is already on stack
EAFA A1                 const one          Q,1
EAFB 0F                 add                Q+1
EAFC 28                 sqr                SQR(Q+1)
     38                 end_calc           ;Switch off calculator
EAFE 010000             LD   BC,#0000      ;Return zero to BASIC via BC
EB01 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

There are many other ways of talking to machine code from BASIC, and
vice versa. I'll illustrate one such method with a routine which
returns the difference in lengths of two strings. The all important
BASIC instruction is this:
	DEF FN A(P$,Q$)=USR address

The trick is to write a machine code routine at the given address in
such a way that whenever FN A(string expression, string expression) is
encountered it will be regarded as ABS(LEN string-LEN string).

How we do this is to look at the way that BASIC treats the arguments
of its user defined (FN) functions. It stores in memory a list of
records, with one record for each parameter required by FN, and in the
same order, with eight bytes for each numeric parameter and nine bytes
for each string parameter. Figure Six [MCCALC4.GIF] illustrates the
format for both types of record, while Figure Seven lists two programs
relevant to this technique. The first program performs the task I have
mentioned as an example (ie. it calculates ABS (LEN string-LEN string).

The second program is more general - it will place on the calculator
stack the value of each function argument in the proper code. It is
intended to be used as a subroutine to be called by your own machine
code programs addressed by DEF FN.

By using a combination of both of these techniques it is possible to
construct a user defined function of more than one argument, and which
returns a string result.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 7
EB02 2A0B5C   LEN_DIFF  LD   HL,(DEFADD)   ;HL points to 1st record
EB05 110600             LD   DE,#0006
EB08 19                 ADD  HL,DE         ;HL points to length of 1st string
EB09 4E                 LD   C,(HL)
EB0A 23                 INC  HL
EB0B 46                 LD   B,(HL)        ;BC= length of 1st string
EB0C 1E08               LD   E,#08
EB0E 19                 ADD  HL,DE         ;HL points to length of 2nd string
EB0F 5E                 LD   E,(HL)
EB10 23                 INC  HL
EB11 56                 LD   D,(HL)        ;DE= length of 2nd string
EB12 EB                 EX   DE,HL         ;HL= length of 2nd string
EB13 A7                 AND  A
EB14 ED42               SBC  HL,BC         ;HL= difference in lengths
EB16 44                 LD   B,H
EB17 4D                 LD   C,L           ;BC= difference in lengths
EB18 D0                 RET  NC            ;Return if result positive
EB19 78                 LD   A,B
EB1A 2F                 CPL
EB1B 47                 LD   B,A
EB1C 79                 LD   A,C
EB1D 2F                 CPL
EB1E 4F                 LD   C,A
EB1F 03                 INC  BC            ;Otherwise negate BC
EB20 C9                 RET

EB21 2A0B5C   GENERAL   LD   HL,(DEFADD)   ;HL points to first record
EB24 23       GEN_LOOP  INC  HL            ;HL points to byte 0E or 24
EB25 7E                 LD   A,(HL)
EB26 FE0E               CP   #0E
EB28 2801               JR   Z,GEN_SKIP    ;Jump if record is numeric
EB2A 23                 INC  HL            ;Skip over extra byte
EB2B 23       GEN_SKIP  INC  HL            ;HL points to five byte form
EB2C CDB433             CALL STACK_NUM     ;Push parameter onto calc. stack
EB2F 7E                 LD   A,(HL)
EB30 FE2C               CP   #2C
EB32 28F0               JR   Z,GEN_LOOP    ;Jump back except for last record
EB34 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

We have not looked at the possibility of returning string results as
yet, but the technique is dead easy. It is similar to the LET P=Q+USR
address technique, but here we have to use LET P$=Q$ AND USR address.
In this case the machine code subroutine will be called with one item,
the string P$, on the calculator stack.

If we alter the topmost item on the stack to a string of our own
choosing, and return to BASIC with BC containing a non-zero value (so
that AND will not empty our string) then P$ in the above example may
be assigned with a string defined in machine code!

It follows then, that you should be able to combine these two
techniques, and come up with a BASIC statement similar to this:
	DEF FN A$(P$,Q)=P$ AND USR address

Of course you can have any number of arguments in the brackets -
including none at all. The arguments can be either string or numeric.
The part of the statement in between "=" and "AND" doesn't have to be
P$ - it can be any string expression - even a string constant. On
entry to your machine code routine the value of this string expression
will be at the top of the calculator stack, whilst the arguments of
the user defined function will be stored as usual as records pointed
to by (DEFADD).

Suppose you wanted a user defined function which had two numeric
arguments and returned a string result - you could then use DEF FN
A$(P,Q)="" AND address. Your own machine code routine would replace
the original (empty) string on the stack with a string of your own
choosing.

Figure Eight lists a user defined function which returns a string
result. The function is referred to as FN F$, and it is the Spectrum
equivalent to the QL's FILL$ function. It requires two arguments - a
string and a number, and it returns the given number of copies of the
string, all joined together (eg. FN F$("*",4) would equal "***").

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 8

DEF FN f$(x$,x)=x$ AND USR fill

EB35 2A0B5C   FILL      LD   HL,(DEFADD)   ;HL points to first record
EB38 110B00             LD   DE,#000B
EB3B 19                 ADD  HL,DE         ;HL points to 5-byte form of number
EB3C CDB433             CALL STACK_NUM     ;Stack number of repeats
EB3F CD991E             CALL FIND_INT2     ;BC= number of repeats
EB42 C5                 PUSH BC
EB43 CDF12B             CALL STK_FETCH     ;BC= length of str; DE= address
EB46 E1                 POP  HL            ;HL= number of repeats
EB47 E5                 PUSH HL            ;Stack number of repeats
EB48 D5                 PUSH DE            ;Stack address of old string
EB49 ED43AE5C           LD   (#5CAE),BC    ;Store length of old string in Mem 5
EB4D 50                 LD   D,B
EB4E 59                 LD   E,C           ;DE= length of old string
EB4F CDF82A             CALL GET_HL*DE_1   ;HL= length of new string
EB52 44                 LD   B,H
EB53 4D                 LD   C,L           ;BC= length of new string
EB54 78                 LD   A,B
EB55 B1                 OR   C
EB56 2005               JR   NZ,NON_EMPTY  ;Jump unless new string is empty
EB58 F1                 POP  AF            ;Delete address of old string
EB59 F1                 POP  AF            ;Delete number of repeats
EB5A C3B62A             JP   STK_STORE     ;Stack empty string and exit
EB5D F7       NON_EMPTY RST  #30           ;DE points to space for new string
EB5E CDB62A             CALL STK_STORE     ;Stack new string parameters
EB61 E1                 POP  HL            ;HL= address of old string
EB62 C1                 POP  BC            ;BC= number of repeats
EB63 C5       FILL_LOOP PUSH BC
EB64 ED4BAE5C           LD   BC,(#5CAE)    ;BC= length of old string
EB68 E5                 PUSH HL
EB69 EDB0               LDIR               ;Make next copy of old string
EB6B E1                 POP  HL
EB6C C1                 POP  BC
EB6D 0B                 DEC  BC            ;BC= number of remaining repeats
EB6E 78                 LD   A,B
EB6F B1                 OR   C
EB70 20F1               JR   NZ,FILL_LOOP  ;Loop back for all copies
EB72 03                 INC  BC            ;Return to BASIC the value one
EB73 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

And now for a numeric function. Once again this is a function which
you cannot define in a BASIC DEF FN statement using BASIC alone. The
function is called the Factorial function. It's a very easy function
to define. The factorial of zero is one; the factorial of one is one;
the factorial of two is 1*2 (=2); the factorial of three is 1*2*3
(=6); the factorial of seven is 1*2*3*4*5*6*7 (=5040), and so on. In
general, the factorial of N (a positive integer) is the product of all
the integers from one to N all multiplied together, or written down
mathematically it is 1*2*3* ... *N. In mathematics the factorial of N
is usually N! (ie. N followed by an exclamation mark) and is
pronounced N factorial. In BASIC, however, we are going to write it as
FN F(N), and we'll define it in machine code using the calculator.

To define it you'll need the BASIC statement
	DEF FN F(N)=USR factorial
somewhere in your BASIC program, and the program of Figure Nine at the
relevant address.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 9

DEF FN f(n)=USR factorial

EB74 2A0B5C   FACTORIAL LD   HL,(DEFADD)   ;HL points to argument record
EB77 23                 INC  HL
EB78 23                 INC  HL
EB79 CDB433             CALL STACK_NUM     ;Push argument onto calc. stack
EB7C CD941E             CALL FIND_INT1     ;A= this argument
EB7F A7                 AND  A
EB80 2001               JR   NZ,FPOSITIVE  ;Jump unless N equals zero
EB82 3C                 INC  A             ;Set A to one, since 0! = 1!
EB83 CD282D   FPOSITIVE CALL STACK_A       ;Push back onto calc. stack
EB86 EF                 RST  #28           ;Switch on calculator
EB87 31                 duplicate
;The calculator now contains two identical numbers, each equal to N.
;If we LET K=N, and if we take M*...*N to mean the product of all integers
;between M and N inclusive, then the items on the calc. stack may be written:
                                           K*...*N,K
     A1       FCT_LOOP  const one          K*...*N,K,1
     03                 subtract           K*...*N,K-1
EB8A 31                 duplicate          K*...*N,K-1,K-1
     30                 eq zero            K*...*N,K-1,K=1?
     0006               jump true,FCT_EXIT Jump if K equals one
     C5                 store M5           K*...*N,K-1 (M5 contains K-1)
EB8F 04                 mult               (K-1)*...*N
EB90 E5                 recall M5          (K-1)*...*N,K-1
EB91 33F6               jump FCT_LOOP      LET K=K-1 and loop back
     02       FCT_EXIT  delete             1*...*N
EB94 38                 end_calc           ;Switch off calculator

;VERSION ONE ENDING
-------------------
EB95 CDA22D             CALL FP_TO_BC      ;BC= required factorial
EB98 DAAD31             JP   C,REPORT_6    ;Error 6 if too big for BC
EB9B C9                 RET

;VERSION TWO ENDING
-------------------
EB95 C1                 POP  BC            ;Bypass STACK_BC subroutine
EB96 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Note that the program has a choice of two alternative endings. Version
one is what we would expect; this is the normal way of ending things,
with all the stacks balanced. This, however, has one big disadvantage
- it can only return integers (which can fit into BC). This means that
we can only calculate up to eight factorial. The second version of the
ending does not suffer from this disadvantage, and any numeric value
whatsoever may be returned to BASIC.

The rules for using this, the best and simplest trick in all
calculatordom, are as follows. The calculator stack must be
unbalanced, with one item too many on the stack. It is the extra
(topmost) item which will be returned to BASIC, so any number (not
just integers) may be returned. The machine stack must also be
unbalanced, with one item too few on the stack. The normal return
address from USR will therefore be bypassed - this bypasses the
subroutine STACK_BC, and hence causes the trick to work. The registers
HL and DE must be correctly assigned, with DE=(STKEND), and
HL=(STKEND)-5.

The "end_calc" instruction which is used to exit from calculator code
will leave HL and DE correctly assigned in this way. The shortest way
to assign DE and HL in this manner if they are not already so set up
is to use the sequence of instructions RST 28 / "end_calc" (in hex EF
followed by 38).

With the second ending in place bigger numbers may be returned. You
may also use this same technique to return decimals (non-integers).
Now you have a complete factorial function defined as FN F. Perhaps
the advantages of the calculator are now beginning to sink in ...


Getting hold of BASIC variables

Getting BASIC variables onto the calculator stack is one of the most
simple things imaginable. The first thing you have to do is to place
the name of the variable, in the form of a string, at the top of the
calculator stack. For instance, the string "A" for the numeric
variable A, the string "B$" for the string variable B$, the string
"TOTAL" for the numeric variable TOTAL, and so on. Then you must load
the B register with either 1D (if the variable is numeric) or 18h (if
the variable is string) and use the sequence of instructions RST 28 /
"execute B" / "end_calc" (in hex EF 3B 38).

This works because 1D is the code for VAL, and 18 is the code for
VAL$. In other words, you are effectively calculating VAL("A"),
VAL$("B$"), VAL("TOTAL"), et cetera. Note that you should not use
"val" or "val$" directly as calculator instructions, unless B is also
assigned with 1D or 18 as before - this is because the calculator
instructions "val" and "val$" are B register dependent (see Appendix
in last month's article).

With single letter variable names, the process is even easier, because
the technique used to create the variable name string is so easy. All
you have to do is place an integer on the calculator stack - the
character code of the letter of the variable name - and use the
calculator "chr$" function to turn it into a string. Figure Ten
contains two programs which demonstrate this idea - the first puts the
value of the BASIC variable A onto the stack, and the second, by a
similar technique, puts the string value of the BASIC variable B$ onto
the stack.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 10
EBBF 061D     VAL_A     LD   B,#1D         ;B= calc. code for VAL
EBC1 EF                 RST  #28           ;Switch on calculator
EBC2 3440B00041         stk_data 41h       ;Stack code of "A"
EBC7 2F                 chr$               ;Convert to string "A"
EBC8 3B                 execute B          ;Convert to value of variable
EBC9 38                 end_calc

EBCA 0618     VAL_B$    LD   B,18          ;B= calc. code for VAL$
EBCC EF                 RST  #28           ;Switch on calculator
EBCD 3440B00042         stk_data 42h       ;Stack code of "B"
EBD2 2F                 chr$               ;Convert to string "B"
EBD3 3440B00024         stk_data 24h       ;Stack code of "$"
EBD8 2F                 chr$               ;Convert to string "$"
EBD9 17                 s_add              ;Combine to form string "B$"
EBDA 3B                 execute B          ;Convert to string value of variable
EBDB 38                 end_calc
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Figure Eleven consists of a table, giving you a single calculator code
instruction which will stack each of the strings "A" to "Z", without
the need for CHR$. Just follow this instruction with "execute B" (code
3B) with B assigned with 1D, and the value of these single letter
numeric variables will be placed on the calculator stack speedily and
efficiently.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 11
To stack numeric variable names on calculator stack:
----------------------------------------------------
VARIABLE  CALCULATOR CODE               VARIABLE  CALCULATOR CODE
--------  ---------------               --------  ---------------
A         34B02B0201                    N         34B00D0201
B         34B0050201                    O         34B01F0201
C         34B0140201                    P         34B0270201
D         34B01B0201                    Q         34B02A0201
E         34B01A0201                    R         34B0120201
F         34B0130201                    S         34B0230201
G         34B00B0201                    T         34B00A0201
H         34B0060201                    U         34B00F0201
I         34B0170201                    V         34B00C0201
J         34B00E0201                    W         34B0220201
K         34B0160201                    X         34B01C0201
L         34B01E0201                    Y         34B0070201
M         34B0150201                    Z         34B0240201
To stack string variable names on calculator stack:
---------------------------------------------------
VARIABLE  CALCULATOR CODE
--------  ---------------
B$        34B0B43E02
W$        34B08B2302
Z$        34B0BD3E02
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Also (perhaps) useful to know is the fact that the strings "B$", "W$",
and "Z$" may be stacked in one single instruction. This means that if
you store a string whose value is needed by machine code in either B$,
W$ or Z$, then its value may be retrieved as easily as PI. Just stack
the string (see Figure Eleven for instructions code) and follow the
instruction with "execute B" (code 3B) with B containing 18. You'll
find that this will save the computer an awful lot of work.

It is of course possible to stack any string in a single instruction,
but to do this the text of the string must be stored separately
somewhere in memory. See last month's article.


Using a BASIC array

There is one final, and very ingenious trick which we can learn. The
trick is to use the calculator's memories in conjunction with a BASIC
array. When we use the calculator instructions "store M0", "recall
M3", et cetera, we are copying five byte numbers between the
calculator stack and the memories. Usually there are six memories,
which are stored amongst the system variables at address MEMBOT, but
this isn't always the case. We can change the whereabouts of the
calculator memories by altering the system variable (MEM). Note that
this system variable always points to the start of memory zero. Memory
one is at (MEM)+5, memory two at (MEM)+0A, and so on.

Figure Twelve contains some BASIC and some machine code. Line 10
creates a BASIC numeric array which, since it is the first variable
encountered, will be the first variable in the VARS area. Line 20 runs
the machine code which follows. The machine code alters the value of
(MEM) so that it points to the variable A(1) in the VARS area. This
has a rather stunning effect.

The array elements have now each acquired two different names, and
each element may be accessed in two different ways. The first element
is known both as A(1) and M0. A(1) is the name used by BASIC, while M0
is the name used by machine code. Similarly, A(2) in BASIC may be
referred to in calculator code as M1, and so on up to A(32), which may
now be referred to in calculator code as M1F. Notice that the number
of available calculator memories has now increased from six to thirty two.

Any number of memories, up to a maximum of thirty two, is possible,
and the number is determined by the dimension of A(). This now means
that any reference to a calculator memory is in fact a direct
reference to an array element. Such elements may be read - or even
altered - by a machine code program.

The program of Figure Twelve will prove this, by assigning - from
within machine code - the element A(16).

Note that the program restores (MEM) to its original value of MEMBOT
before returning to BASIC. This should always be done. Note also that
since A(1) to A(3) are equivalent to M0 to M2, they are liable to be
corrupted by such functions as SIN etc. (see Appendix in last month's
article).

I think that's all there is to say for now on the subject of passing
parameters between BASIC and machine code. I'll move on now to a new
topic.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;Figure 12

10 DIM a(32)                               A() is first item in VARS area
20 RANDOMIZE USR array                     Execute machine code

EBDC 2A4B5C   ARRAY     LD   HL,(VARS)     ;HL points to BASIC array A()
EBDF 010600             LD   BC,#0006
EBE2 09                 ADD  HL,BC         ;HL points to first element of A()
EBE3 22685C             LD   (MEM),HL      ;Identify memories with elements
EBE6 EF                 RST  #28           ;Switch on calculator
EBE7 3440B0002A         stk_data 2A        ;Stack the number 2A
     CF                 store MF           ;Store in memory 0F
     02                 delete             ;Empty calculator stack
EBEE 38                 end_calc           ;Switch off calculator
EBEF 21925C             LD   HL,MEMBOT
EBF2 22685C             LD   (MEM),HL      ;Restore calculator memories
EBF5 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


The function generator

There is just one calculator instruction left to cover. It is the
function "series", otherwise known as the function generator, or
series generator. Its code will be a value between 80h and 9F. The
last five bits of the code form a parameter, so that 86 means "series
6", 8C means "series 12d", 99 means "series 25d", and so forth. This
is the single most powerful instruction in the whole of the calculator
set. It is the function with which SIN and EXP and others were written
in the ROM. With it we may create our own designer functions, or
implement mathematical functions which are not present (and not
otherwise possible) on the Spectrum. The "series" function is the
calculator's final frontier. To be continued ...
