[14 Apr 1996, 03:45pm]
These pages are under construction, where you see moof! it means that the section isn't yet started. Most of the written sections need changing in one way or another and the fonts used mess up on some browsers (ArcWeb). (If you're wondering, moof! is the call of the ledgendary dogcow.) Suggestions about this page will be gratefully accepted.
BBC BASIC is a powerful beast - fast, flexible, easy to use and includes an assembler. I have created this page, to show you techniques that will allow you to write more efficient programs, which use the BASIC interpreter to its full capabilities.
Some of the techniques demonstrated here are not what you might call 'structured' programming. :-) They employ very hacky methods to get the job done, but they do work and have enabled me to write some very strange looking, but compact, code.
I don't own the BASIC Reference manual, so I don't know if there's any overlap with what's here.
If you use a LOCAL
statement in a procedure or function, then don't assume that the variables you specify will be created and initialised for you, because there's no guarantee that it will. This is true even if your variables exist before the procedure or function is called - you will still need to initialise them.
The DIM statement is often used incorrectly, for example consider DIM b% 256
, for which BASIC allocates a 260 bytes. The parameter passed to DIM is based at 0, so if you want to allocate an n byte block you should specify n-1 bytes in the DIM statement. BASIC always allocates blocks in multiples of four bytes, so non-word-aligned values will always be rounded up to a word boundary, hence in the above example, the DIM statement tells BASIC to allocate 257 bytes and BASIC rounds this up to 260.
Remember that the first element of an array or DIMmed block is at zero, not one.
I think of this as obvious, but I'll point it out anyway: Use Integer variables in preference to Real variables wherever possible - they're faster. Although in most cases, they use the same amount of memory - both use 12 bytes for a single-character variable name. (BASIC VI uses 16 bytes for a Real variable as its real variables are stored as actual floating-point numbers.)
The STRING$(<number>,<string>) statement can be used to create temporary buffers on the fly, for example when calling a SWI which needs a string buffer for output. However, their maximum length is 255 which is one less than the 256 character buffer you usually need. For example :
DEF FNmsg(msg$)
LOCAL b%,l%
SYS "XMessageTrans_Lookup",msg%,msg$+":feck!",STRING$(255,"*"),256 TO ,,b%,l%
b%?l%=13
=$b%
Using a statement such as DIM b% -1
will set aside no space for b%
, but will set b%
to the address of the start of the next free location. This value is the last byte+1 of the last block allocated. This makes it easy to set aside space for assembly and assign the L%
range-check value simultaneously. e.g. DIM code% 256, L% -1
. As BASIC always allocates blocks as multiples of four bytes, b% will always be word aligned.
Other negative values in DIM statements will generate an error.
TRACE is a very useful statement. It's used as follows :
TRACE [STEP] ON|OFF|PROC|<number>
TRACE TO <string>
TRACE CLOSE
TRACE
By using BPUT#TRACE,"string"
then "string" will be sent to the trace file. Typically you would write IF TRACE THEN BPUT#TRACE,"string"
.
Using an integer as a set of flags is much more efficient than using several varaibles to do the same job.
Using the >> operator to shift down the topmost bit (or something which includes the topmost bit) of a word will not work, as it is a signed operator. Instead use the >>> operator which will shift down unsigned. (There is no equivalent <<< operator, as it is meaningless.)
Where operator priority allows, you can often omit brackets. For example, the following code :
(thom*stuart)<<(is+the-devil)
can be rewritten as the following :
thom*stuart<<is+the-devil
which will still yeild the same result, as the numerical operators all have a higher priority than the shift (<<) operator.
Brackets can be omitted on most functions e.g. LEN(file$)
becomes LENfile$
Using the more recent versions of system calls is likely to provide performance increases and enhanced functionality. For example :
IF risc_os_350% THEN
SYS "Wimp_ResizeIcon"...
ELSE
SYS "Wimp_DeleteIcon"...
misc%!4=win%:misc%!16=x%:...
SYS "Wimp_CreateIcon"...
ENDIF
Another example is the RISC OS 3.6 sprite plot calls which can now use a simple ordered-dither, if you specify a bit in R0.
Use <value> AND NOT <bitmask>
to clear bits in Integer variables. You can also use statements such as <value> AND-4
as -1 is equivalent to &FFFFFFFF.
<>0
<>0
can be omitted in most cases if it corresponds to a boolean state. For example, IF n<>0 THEN ...
can be replaced with IF n THEN ...
. This works as zero is interpreted to be FALSE and any non-zero is interpreted to be TRUE (it's actual value is -1.) All the IF statement does is check if the expression given to see if it is TRUE or FALSE.
You access memory using the !
(word at), ?
(byte at) and |
(real at) operators. Instead of using ?(address+n)
you can use address?n
. address?0
is equivalent to ?address
.
Using the << shift command to replace multiplications by power-of-two numbers, yeilds a speedup.
Although you can write code that looks like this :
IF expression THEN variable=TRUE ELSE variable=FALSE
use your brain! With a bit of thought you can see that an exact equivalent is :
variable=(expression)
There's usually plenty of code which can be reduced like this.
If you're going to call a routine or expression more than, say, three times for the same value, use a variable.
Beware of trailing spaces after THEN's.
THEN
statementsBASIC allows you to omit the THEN statement on a single-line IF structure. If you want to make sure your program will be handled correctly when passed through a BASIC compressor program, you must always use the THEN
statement. This is because expressions such as IF x<>0 E%=1
can get compressed to IFx<>0E%=1
. This will confuse the interpreter, as it will think that the '0E' is a number.
RETURN
statementmoof!
moof!
moof! Notes: EOR = 7; OR = 7; AND = 6; NOT = ?; (shifts) = ?
moof!
Try to keep redraw as fast as possible, e.g. by ommitting Sprite plotting translation tables and using the faster plotting call.
moof!
moof!
The natural way to code a block memory movement procedure in BASIC is to write a FOR loop with a load/store pair inside. However, this is a slow way to do it. Using a small ARM code block copier, will make (moof! ~ expansion rqd.) whereever possible esp. for block memory moves and processing, but keep it in a file to save execution time.
Note: assembler area limit checking, exported routines from CALL
moof!
moof!
moof! Notes: RESTORE+, LOCAL DATA, RESTORE DATA (automatic) - may be used anywhere.
moof! Notes: ERROR EXT <number>,<string>, LOCAL ERROR, RESTORE ERROR (automatic)
moof! Notes: a()=1,2,3 / "last subscript changes quickest"
transformation()=1,0,0,0,1,0,0,0,1
coordinate()=coordinate()*transformation()
SUMLEN which returns the sum of the lengths of all the strings in a string array.
PRINT"There are ";SUMLEN(A$())" characters."
MOD which returns the modulus (square root of the sum of the squares of each element) of a numeric array.
normalisedvector()=vector()/MODvector()
QUIT function: TRUE if BASIC has been called with -quit on command line.
Notes: @% is now reported by LVAR in ANSI printf format - one of g, e or f formats followed by the field width, a dot and the precision. @% is also settable using this format - @%="G10.9" corresponds to the default. There is now a HELP message on @% and the formats.
Notes: BASIC$Crunch
If you know of any techniques which aren't included here and you would like added to the list, mail me with the details. All (polite ;-) suggestions will be gratefully received.