BASIC Programming

This page is a set of hints and tips for programming in BBC BASIC V/VI.

[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.

Index

  1. LOCAL variables
  2. Correct use of the DIM statement
  3. Integers
  4. Dynamic STRING$() buffers
  5. Negative DIM statement
  6. The TRACE statement
  7. Bitfields
  8. Correct use of shift operators (>>, << and >>>)
  9. Omitting brackets
  10. Using newer OS calls
  11. Bit-clearing
  12. Omitting <>0
  13. Accessing memory
  14. Shift multiplication
  15. Being clever 8-)
  16. Popular functions/expressions
  17. Trailing spaces
  18. Omitting THEN statements
  19. Using the RETURN statement
  20. Memory use
  21. Operator priorities
  22. Formatting using LISTO
  23. Make window redraw fast
  24. Avoid DIMs
  25. Don't get lazy
  26. Use of ARM
  27. constants
  28. using-overlay
  29. data
  30. errors
  31. array-initialisation
  32. sumlen
  33. mod
  34. quit
  35. at-percent
  36. end-equals
  37. crunch

LOCAL variables

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.

Correct use of the DIM statement

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.

Integers

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.)

Dynamic STRING$() buffers

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%

Negative DIM statement

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.

The TRACE statement

TRACE is a very useful statement. It's used as follows :

TRACE [STEP] ON|OFF|PROC|<number>
Trace [in single step mode] on or off or procedure or function calls or lines below the number.
TRACE TO <string>
Send all output to stream <string>.
TRACE CLOSE
Close stream output.
Expression: TRACE
Gives handle of the stream.

By using BPUT#TRACE,"string" then "string" will be sent to the trace file. Typically you would write IF TRACE THEN BPUT#TRACE,"string".

Bitfields

Using an integer as a set of flags is much more efficient than using several varaibles to do the same job.

Correct use of shift operators (>>, << and >>>)

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.)

Omitting brackets

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 newer OS calls

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.

Bit-clearing

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.

Omitting <>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.

Accessing memory

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.

Shift multiplication

Using the << shift command to replace multiplications by power-of-two numbers, yeilds a speedup.

Being clever 8-)

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.

Popular functions/expressions

If you're going to call a routine or expression more than, say, three times for the same value, use a variable.

Trailing spaces

Beware of trailing spaces after THEN's.

Omitting THEN statements

BASIC 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.

Using the RETURN statement

moof!

Memory use

moof!

Operator priorities

moof! Notes: EOR = 7; OR = 7; AND = 6; NOT = ?; (shifts) = ?

Formatting using LISTO

moof!

Make window redraw fast

Try to keep redraw as fast as possible, e.g. by ommitting Sprite plotting translation tables and using the faster plotting call.

Avoid DIMs

moof!

Don't get lazy

moof!

Use of ARM

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

Constant variable reduction

moof!

Using the LIBRARY, OVERLAY, and INSTALL systems

moof!

DATA

moof! Notes: RESTORE+, LOCAL DATA, RESTORE DATA (automatic) - may be used anywhere.

Errors

moof! Notes: ERROR EXT <number>,<string>, LOCAL ERROR, RESTORE ERROR (automatic)

Array initialisation

moof! Notes: a()=1,2,3 / "last subscript changes quickest"

transformation()=1,0,0,0,1,0,0,0,1
coordinate()=coordinate()*transformation()

SUMLEN

SUMLEN which returns the sum of the lengths of all the strings in a string array.

PRINT"There are ";SUMLEN(A$())" characters."

MOD

MOD which returns the modulus (square root of the sum of the squares of each element) of a numeric array.

normalisedvector()=vector()/MODvector()

QUIT

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.

END=

CRUNCH

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.

Back to Dave's Home Page