Advanced EhBASIC techniques

Enhanced BASIC, advanced examples
Creating buffer space.
Sometimes there is a need for a byte oriented buffer space. This can be achieved by lowering the top of BASIC memory and using the “protected” space created thus. The main problem with this is that there may not be the same RAM configuration in all the systems this code is to run on.

One way round this is to allocate the space from BASIC’s array memory by dimensioning an array big enough to hold your data. As arrays always start from zero then to work out the array size needed you do ..
Array dimension = (bytes needed/4)-1.
E.g.

10 DIM b1(19) : REM need 80 bytes for input buffer
20 DIM b2($100) : REM need $0400 bytes for screen buffer

So you’ve allocated the buffer but where is it? This is one use of the VARPTR function, it is used in this case to return the start of the array’s data space.
E.g.

100 a1 = VARPTR(b1(0)) : REM get the address of the buffer space

But wait, there is another problem here. Because variables are created when they are first assigned a value any new variable created after the array is dimensioned will move the array in memory. So the following will not work..

10 DIM b1(19) : REM 80 bytes for buffer
20 a1 = VARPTR(b1(0)) : REM get the address of the buffer space
40 FOR x = 0 to 79
50 POKE a1+x,ASC(" ")
60 NEXT
.
.

When we get to line 40, a1, the pointer to the array data space, is wrong because the variable x has been created and moved all the arrays up by six bytes.

The way round this is to ensure that all variables that you will use have been created prior to getting the pointer. This also means you start with known values in all your variables.

10 DIM b1(19) : REM 80 bytes for buffer
20 x = 0 : REM loop counter
30 a1 = VARPTR(b1(0)) : REM get the address of the buffer space
40 FOR x = 0 to 79
50 POKE a1+x,ASC(" ")
60 NEXT
.
.

Another way is to get the pointer every time you use it. This has the advantage of
always being correct but is somewhat slower.

10 DIM b1(19) : REM 80 bytes for buffer
40 FOR x = 0 to 79
50 POKE VARPTR(b1(0))+x,ASC(" ")
60 NEXT
.
.

One thing to remember, never try to use a string array as a buffer. Everything will
seem to work until you run out of string space and the garbage collection routine is
called. Once this happens it’s likely that your buffer will get trashed and you may
even find that the program freezes because the garbage collection routine now thinks
that there are more string bytes than there are memory.

Creating short code space.
While the techniques explained above can also be used to create space for machine code
routines there is a simpler way for position independant routines up to 255 bytes long
to be held in memory.

Assemble the code and use the hex output from your assembler to create a set of BASIC
data statements.
E.g.

1000 DATA $A5,$11,$C9,$3A,$B0,$08,$38,$E9
1010 DATA $30,$38,$E9,$D0,$90,$0D,$09,$20
1020 DATA $38,$E9,$61,$90,$0B,$C9,$06,$B0
1030 DATA $07,$69,$3A,$E9,$2F,$85,$11,$60
1040 DATA $18,$60
1050 DATA -1

Now we just use a loop like this to load this hex code into a string.

10 RESTORE 1000
20 READ by : REM assume at least one byte
30 DO
40 co$ = co$+CHR$(by)
50 READ by
60 LOOP UNTIL by=-1

The code can now be called by doing ..

140 CALL(SADD(co$))

Note that you must always use the SADD() function to get the address for the CALL as the
garbage collection routine may move the string in memory and this is the best way to ensure that the address is always correct.

Coding for speed

Spaces
Remove spaces from your code. Spaces, while they don’t affect the program flow, do take a finite time to skip over. The only space you don’t need to worry about is the one between the line number and the code as this is stripped during input parsing and the apparent space is generated by the LIST command output.

E.g. the following ..

10 REM line 10
20   REM line 20
30      REM line 30

.. reads as follows when LISTed

10 REM line 10
20 REM line 20
30 REM line 30

Removing REM.
Remove remarks from your code. Remarks like spaces don’t do anything, program wise, but take time to skip. Removing remarks, especially from time critical code, can
make a big difference.

Variables.
Use variables. One place where time is wasted, especially in loops, is repeatedly interpreting numeric values or unchanging functions.
E.g.

.
140 FOR x = 0 to 79
150 POKE $F400+x,ASC(" ")
160 NEXT
.

This loop can be improved in a number of ways. First assign a variable the value $F400 and use that. Doing this is faster after only three uses.
E.g.

10 a1 = $F400
.
140 FOR x = 0 to 79
150 POKE a1+x,ASC(" ")
160 NEXT
.

The other way to make this loop faster is to assign the value of the (unchanging) function to a variable, then move the function outside the loop.
E.g.

10 a1 = $F400
.
130 sp = ASC(" ")
140 FOR x = 0 to 79
150 POKE a1+x,sp
160 NEXT
.

Now the ASC(” “) is only evaluated once and the loop is executed faster.

GOTO and GOSUB
When EhBASIC encounters a GOTO or GOSUB it has to search through memory for the target line. If the target line follows the command then it searches from the next
line, if the target line precedes the command then the search starts from the beginning of program memory. So keeping this distance, in lines, as short as possible will make the program run faster.

One place that this is difficult is in a conditional loop. In calculating points in the Mandelbrot set, for example, code like this is used ..

.
230 INC it
235 tp = mx*mx-my*my+x
240 my = 2*mx*my+y
245 mx = tp
250 co = (mx*mx + my*my)
255 IF (it<128) AND (co<4.0) THEN 230
.

Each time the condition in line 255 is met the interpreter has to search from the start of memory for line 230. While this may not take long if the program is short it can slow longer programs considerably.

This can easily be resolved though by using a DO .. LOOP instead.
So our example code becomes..

.
220 DO
230 INC it
235 tp = mx*mx-my*my+x
240 my = 2*mx*my+y
245 mx = tp
250 co = (mx*mx + my*my)
255 LOOP WHILE (it<128) AND (co<4.0)
.

This is quicker because the location of the start of the loop, the DO, is placed on the stack and the interpreter doesn’t have to search for it.

Packing them in.
Another way to speed up time critical code is to place as many commands as possible on each line, this can make a noticeable speed gain.
E.g.

10 a1 = $F400
.
130 sp = ASC(" ")
140 FOR x = 0 to 79 : POKE a1+x,sp : NEXT
.

INC and DEC.
INCrement and DECrement are quick and clear ways of altering a numeric value by plus or minus one and are faster than using add or subtract.
E.g.

100 INC a

.. is quicker than ..

100 a = a+1

.. and ..

100 INC a,a

.. is still quicker than ..

100 a = a+2

Also combine increments or decrements if you can.
E.g.

100 INC so,d

.. is quicker than ..

100 INC so : INC de

>> and <<
Using >> and << can be quicker than using / or * where integer math and a power of two is involved.
E.g. you want to find the byte that holds the pixel at x,y in a 256 x 32 display

100 ad = y*32 + INT(x/8) : REM pixel address

.. is done quicker with.

100 ad = y&amp;amp;lt;&amp;amp;lt;5 + x&amp;amp;gt;&amp;amp;gt;3 : REM pixel address

Coding for space
Most of the techniques used to improve the speed of a program can also reduce the number of bytes used by that program.

Spaces.
Remove spaces from your code. The only space you don’t need to worry about is the one between the line number and the code as this is stripped during input parsing and the apparent space is generated by the LIST command output.

Removing REM.
Remove remarks from your code. Remarks like spaces don’t do anything, removing remarks,
can save a lot of space.

Variables.
Use variables. Often you will find yourself using the same numeric value again and again. If this value has many digits, such as the value for e (2.718282), then
assigning that value at the beginning of the program can start to save space with the third use.

Re-use variables. Every time you assign a new variable a value it takes up six more bytes of the available memory. If you have a variable that is only used as a loop
counter then try to use it for temporary values or GET values elsewhere in the program.

Constants.
There are two constants defined in EhBASIC, PI and TWOPI. They are the closest
floating values to pi and 2*pi and will save you seven bytes each time you can
use them.

Packing them in.
Another way to save space is to place as many commands as possible on each line,
this will save you five bytes every time you put another command on an existing
line compared to using a new line.

INC and DEC.
INCrement and DECrement also save space. Either will save you three bytes for
each variable INCremented or DECremented.

Derived functions
The following functions, while not part of BASIC, can be calculated using the
existing BASIC functions.

Secant SEC(X)=1/COS(X)
Cosecant CSC(X)=1/SIN(X)
Cotangent COT(X)=1/TAN(X)
Inverse sine ARCSIN(X)=ATN(X/SQR(X*X+1))
Inverse cosine ARCCOS(X)=-ATN(X/SQR(X*X+1))+PI/2
Inverse secant ARCSEC(X)=ATN(SQR(X*X-1))+(SGN(X)-1)*PI/2
Inverse cosecant ARCCSC(X)=ATN(1/SQR(X*X-1))+(SGN(X)-1)*PI/2
Inverse cotangent ARCCOT(X)=-ATN(X)+PI/2
Hyperbolic sine SINH(X)=(EXP(X)-EXP(-X))/2
Hyperbolic cosine COSH(X)=(EXP(X)+EXP(-X))/2
Hyperbolic tangent TANH(X)=-EXP(-X)/(EXP(X)+EXP(-X))*2+1
Hyperbolic secant SECH(X)=2/(EXP(X)+EXP(-X))
Hyperbolic cosecant CSCH(X)=2/(EXP(X)-EXP(-X))
Hyperbolic cotangent COTH(X)=EXP(-X)/(EXP(X)-EXP(-X))*2+1
Inverse hyperbolic sine ARCSINH(X)=LOG(X+SQR(X*X+1))
Inverse hyperbolic cosine ARCCOSH(X)=LOG(X+SQR(X*X-1))
Inverse hyperbolic tangent ARCTANH(X)=LOG((1+X)/(X))/2
Inverse hyperbolic secant ARCSECH(X)=LOG((SQR(X*X+1)+1)/X)
Inverse hyperbolic cosecant ARCCSCH(X)=LOG((SGN(X)*SQR(X*X+1)+1)/X)
Inverse hyperbolic cotangent ARCCOTH(X)=LOG((X+1)/(X-1))/2