RTR logo

BBC BASIC for SDL 2.0

BBC BASIC for Windows

General Information



Line numbers

Line numbers are optional in BBC BASIC for Windows and BBC BASIC for SDL 2.0. A line must be numbered (or labelled) if it is referenced by a GOTO, GOSUB or RESTORE statement but otherwise the line number may be omitted. It can also be useful to number lines when debugging a program, since error messages can contain the number of the line in which the error occurs.

It is never necessary to use line numbers. Instead of GOSUB you should use named user-defined procedures or functions. In the case of RESTORE you can use the relative option of the statement. You can avoid GOTO by making use of structures like REPEAT ... UNTIL and WHILE ... ENDWHILE and using the techniques described under Program Flow Control.

The Renumber utility can be used to add or remove line numbers automatically. Permissible line numbers are in the range 1 to 65535.


Statement separators

If you want to write more than one statement on a line, the statements should be separated by a colon (:). BBC BASIC for Windows and BBC BASIC for SDL 2.0 will sometimes tolerate the omission of the colon if this does not lead to ambiguity, but it's safer to leave it in and the program is easier to read.

For example, the following will work:

FOR i=1 TO 5 PRINT i : NEXT
but it's better to include the colon:
FOR i=1 TO 5 : PRINT i : NEXT
You may prefer to write each statement on a separate line. This will not impair the performance of a compiled program so long as you select the 'concatenate lines' Crunch option.


Line continuation

BBC BASIC's (tokenised) program lines are limited to 251 characters, excluding the line number. Although this is usually adequate, occasionally you may want to use longer lines or to split a program line over several screen lines for improved readability. You can do that by using the line continuation character \ (backslash) as follows:
PROC_render(pdev%,  \ pointer to Direct3D device
\           nobj%,  \ number of objects
\           yaw(),  \ array of yaw angles
\           pitch(),\ array of pitch angles
\           roll()  \ array of roll angles
\          )
Here a single procedure call has been split between several lines for reasons of clarity and to allow the various parameters to be commented. Everything after the continuation character is ignored by BASIC when the program is executed so comments can conveniently be put there. Note that the continuation lines themselves must begin with another \ character.

You can use the line continuation character almost anywhere that a space would be allowed, but not:

You can use the continuation character in a single-line IF statement or in an ON statement (e.g. ON GOSUB, ON SYS) but you must not put anything (not even a comment) after the continuation character. If the IF or ON statement has an ELSE clause, the continuation character can only be used after the ELSE.


Expression priority

Order of evaluation

The various mathematical and logical operators have a priority order. BBC BASIC will evaluate an expression taking this priority order into account. Operators with the same priority will be evaluated from left to right. For example, in a line containing multiplication and subtraction, ALL the multiplications would be performed before any of the subtractions were carried out. The various operators are listed below in priority order (highest priority at the top):

variables  functions  ()  !  ?  &  %  unary+−  NOT
^
*  /  MOD  DIV
+  −  SUM
=  <>  <=  >=  >  <  <<  <<<  >>  >>>
AND
EOR  OR

Examples

The following are some examples of the way expression priority can be used. Including brackets even when they are not strictly necessary can make the intention clearer, but will slightly slow down program execution.

IF A=2 AND B=3 THEN
IF ((A=2)AND(B=3))THEN

IF A=1 OR C=2 AND B=3 THEN
IF((A=1)OR((C=2)AND(B=3)))THEN

IF NOT(A=1 AND B=2) THEN
IF(NOT((A=1)AND(B=2)))THEN

N=A+B/C-D
N=A+(B/C)-D

N=A/B+C/D
N=(A/B)+(C/D)

Array arithmetic

A limited number of arithmetic and logical operations are available which operate on entire arrays rather than on single values. These are addition (+), subtraction (-), multiplication (*), division (/), logical OR, logical AND, logical exclusive-or (EOR), integer quotient (DIV), integer remainder (MOD) and the dot product (.). For example:
C%() = A%() * B%() + C%()
B() = A() - PI
A() = B() . C()
A$() = B$() + C$() + D$()
A() = B() * 2 / C() + 3 - A()
Array expressions are evaluated strictly left-to-right, so higher priority operators must come first and brackets cannot be used to override the order of execution:
C%() = C%() + B%() * A%() : REM not allowed
C%() = A%() * (B%() + C%()) : REM not allowed
All arrays must be DIMensioned before use, and (with the exception of the dot product) the number of elements in all the arrays within an expression must be the same; if not, the Type mismatch error will result. In the case of the dot product the rules are as follows: The SUM function returns the sum of all the elements of a numeric array, so the mean value can be found as follows (assuming a one-dimensional array):
mean = SUM(A()) / (DIM(A(),1) + 1)
The MOD function returns the modulus (square-root of the sum of the squares of all the elements) of a numeric array, so an array can be normalised as follows:
A() = A() / MOD(A())
Don't confuse this with the MOD operator, which returns the remainder after an integer division.

You can use the compound assignment operators with arrays (e.g. +=, −=,*= or /=) but there is an important restriction. If the array is an integer (or byte) array the expression on the right-hand-side of the operator is converted to an integer before the operation is carried out. So the statement:

A%() *= 2.5
will multiply all the elements of array A%() by 2, not by 2.5.


Initialising arrays

You can initialise the individual elements of an array in exactly the same way as you would normal variables. However you can also initialise the contents of an entire array in one operation:
A%() = 1, 2, 3, 4, 5
B$() = "Alpha", "Beta", "Gamma", "Delta"
C() = PI
If there are fewer values supplied than the number of elements in the array, the remaining elements are unaffected. However in the special case of a single value being supplied, all the elements of the array are initialised to that value. So in the last example all the elements of C() will be initialised to PI. There must be enough free space on the stack to contain a copy of the array.


Variables

Variable names

Variable names may be of any length, limited only by the maximum length of a line, and all characters are significant. They must start with a letter (A..Z, a..z) an underline (_) or a ` character (CHR$96). The remaining characters must be A..Z, a..z, 0..9, underline or ` (CHR$96). Variable names are case-sensitive ('number', 'Number' and 'NUMBER' are different variables). Variable names must not begin with a BASIC keyword (with some exceptions).

Using long, meaningful, variable names will make your program easier to understand and debug, and will not affect performance so long as the program is compiled with the abbreviate names crunch option.

Naming conventions

You are recommended to adopt the following conventions when naming variables: Following these recommendations will make your programs easier to understand and avoid problems such as an interrupt routine erroneously accessing a local variable instead of a global variable.

Variable types

BBC BASIC for Windows and BBC BASIC for SDL 2.0 use the following types of variable:

Note that in BBC BASIC the different types of variable are completely independent. For example, the integer variable list%, the numeric variable list, the string variable list$ and the arrays list%(), list() and list$() are all entirely separate.

Static variables

The variables A%..Z% inclusive are called static variables. They are a special type of integer numeric variable which are not cleared by the statements RUN, CHAIN or CLEAR. As such, these variables can be useful for transferring values between programs or for holding information which must be retained when the rest of the variables are cleared. They are also accessed slightly more quickly than other variables.

In addition the variables A%, B%, C%, D%, F%, X% and Y% have special uses in the CALL and USR routines, and L%, O% and P% have special meaning in the assembler.

Static variables are stored in 32 bits and can contain any whole number from −2147483648 to +2147483647.

Integer numeric variables

Integer numeric variables have names which end with a percent sign (%). They are stored in 32 bits and can contain any whole number in the range −2147483648 to +2147483647. It is not necessary to declare a variable as an integer for advantage to be taken of fast integer arithmetic. For example, FOR ... NEXT loops execute at integer speed whether or not the control variable is an 'integer variable' (% type), so long as it has an integer value.

64-bit integer numeric variables

64-bit integer numeric variables have names which end with a pair of percent signs (%%). They can contain any whole number in the range −9223372036854775808 to +9223372036854775807.

Byte numeric variables

Byte numeric variables have names which end with an ampersand sign (&). They are stored in 8 bits and can contain any whole number in the range 0 to +255 (byte variables are unsigned). Byte variables are particularly useful as structure members, because they can be used as building blocks to create data structures of any size. When assigning a value to a byte variable, it is evaluated modulo-256 (equivalent to AND &FF).

Variant numeric variables

Variant numeric variables have no suffix character and can contain either integers or real (floating-point) values.

On x86 platforms (Windows, MacOS, Linux) variant numeric variables can contain either a 64-bit signed integer value or an 80-bit (10-byte) floating-point value consisting of a 64-bit mantissa and a 16-bit exponent, with a range of approximately ±3.4E−4932 to ±1.1E4932 and a precision of approximately 19 significant figures.

On ARM platforms (Raspberry Pi, Android, iOS) and the in-browser edition, variant numeric variables can contain either a 64-bit signed integer value or a 64-bit (8-byte) floating-point value consisting of a 53-bit mantissa and an 11-bit exponent, with a range of approximately ±2.3E−308 to ±1.7E308 and a precision of approximately 15 significant figures.

An explanation of how variant numeric variables are stored is given in the Format of data in memory section.

64-bit ('double') floating-point variables

Double numeric variables have names which end with a hash sign (#); they contain a 64 bit (8 byte) floating-point value consisting of a 53-bit mantissa and an 11-bit exponent. They have a range of approximately ±2.3E−308 to ±1.7E308 and a precision of approximately 15 significant figures.

String variables

String variables have names which end in a dollar sign ($); their length is limited only by the amount of available memory (in BBC BASIC for Windows version 5.95a or earlier their maximum length is 65535 characters). An explanation of how string variables are stored is given in the Format of data in memory section.

Arrays

Arrays of variant numeric, double, integer, byte and string types are allowed. All arrays must be dimensioned before use. Variant numerics, doubles, integers, bytes and strings cannot be mixed in a multi-dimensional array; you have to use one array for each type of variable you need. A double array is indicated by a name ending in a hash sign (#), an integer array by a name ending in a percent sign (% or %%), a byte array by a name ending in an ampersand (&) and a string array by a name ending in a dollar sign ($).

The value given when the array is declared is the maximum value that the subscript can take. Since the minimum value for a subscript is zero, the total number of elements in that dimension is equal to the given value plus one. For example, the two-dimensional array declared with:

DIM marks%(10,5)
has a total of 66 elements (10+1 rows by 5+1 columns).

Pseudo-variables

Pseudo-variables are keywords which behave like variables. They are LOMEM, HIMEM, PAGE, PTR and TIME/TIME$. Their values may be written (e.g. TIME = 0) or read (e.g. T = TIME) depending on the context. Pseudo-variables cannot be used as formal parameters of functions or procedures, cannot be made LOCAL, cannot be used as the control variable of a FOR statement, cannot be passed as a parameter to CALL and cannot be assigned in INPUT, MOUSE, READ or SYS statements.

System variables

System variables have names which begin with the '@' sign. System variable names are predefined: you cannot create your own. The best known system variable - and the only one in the original version of BBC BASIC - is @%. This variable controls print formatting; see the description of the PRINT statement for details.

The other system variables are as follows. They are mostly of use when accessing Windows or SDL 2.0 API functions from BASIC.

NameValue
@chrmap%A pointer to the 'character map' used in MODE 7 and for GET(x,y)
@flags%An integer incorporating BASIC's control flags. The MSB, if set, indicates that there is a pending Escape condition
@fn%(n)An array of pointers to internal functions
@hfile%(n)An array of file handles indexed by channel number
@hpal%The handle of (BB4W) or pointer to (BBCSDL) the colour palette
@hwnd%The 'window handle' for BASIC's program (output) window
@hwo%The handle of the WAVEOUTPUT device
@ispal%A Boolean which is non-zero if the display is paletted
@memhdc%The 'device context' or 'renderer' for BASIC's output canvas
@msg%The MSG value (for use with ON MOUSE, ON MOVE and ON SYS)
@wparam%The WPARAM value (for use with ON MOUSE, ON MOVE and ON SYS)
@lparam%The LPARAM value (for use with ON MOUSE, ON MOVE and ON SYS)
@ox%The horizontal offset (in pixels) between the output bitmap and the canvas. Must be zero or positive
@oy%The vertical offset (in pixels) between the output bitmap and the canvas. Must be zero or positive
@vdu%A pointer to BASIC's text and graphics parameters (see below)
@cmd$The command line of a 'compiled' program
@dir$The directory (folder) from which your program was loaded
@lib$The directory (folder) containing the BBC BASIC library files
@tmp$The temporary directory (folder)
@usr$A directory (folder) specific to the current user
@vdu{}A structure containing the main VDU variables
 
BBC BASIC for Windows only:
@haccel%The handle of the keyboard accelerator, if used
@hcsr%The handle for the mouse pointer (cursor)
@hevent%The handle of the event used to prevent blocking in serial and parallel I/O
@hmdi%The Multiple Document Interface window handle (if any)
@hwacc%The window handle to which keyboard accelerator commands should be sent
@midi%The MIDI device ID (non-zero if a MIDI file is playing)
@prthdc%The 'device context' for the current printer (if any)
 
BBC BASIC for SDL 2.0 only:
@cache%The memory address of the TTF font cache (65536 texture pointers).
@panx%The horizontal position of the output canvas with respect to the window.
@pany%The vertical position of the output canvas with respect to the window.
@zoom%The scale factor of the output canvas with respect to the window (&8000 = 1:1).
@platform%The SDL version number in the most-significant 24 bits (major, minor, patch) and a platform identifier in the LS 4 bits (0 = Windows, 1 = Linux, 2 = MacOS, 3 = Android, 4 = iOS, 5 = in-browser). If bit 6 is set this indicates a 64-bit platform.

On 64-bit platforms @chrmap%, @fn%(), @hfile%(), @hwnd%, @hpal%, @memhdc%, @vdu% and @cache%, may all return 64-bit values, despite them having only a single % suffix!

The @cmd$ variable

The variable @cmd$ allows an executable created with the Compile utility to access the command line with which it was executed. @cmd$ is empty in the case of a program run from the interactive environment. @cmd$ is a read-only variable, you must not write to it.

The @dir$ and @lib$ variables

The variables @dir$ and @lib$ are useful when INSTALLing libraries or when loading any other resource files (images, data files etc.) needed by your program. If you ensure that these files are stored either in the library directory or in the same directory as the program itself you can simply prefix their filenames with @lib$ or @dir$ respectively:
INSTALL @lib$+"MYLIB"
OSCLI "DISPLAY "+@dir$+"MYIMAGE"
SYS "PlaySound", @dir$+"MYSOUND", 0, &20001 
Files specified this way are automatically incorporated into an executable created by the Compile command. @dir$ and @lib$ are pre-defined, you must not assign new values to them.

The @tmp$ and @usr$ variables

The variables @tmp$ and @usr$ are useful when storing data files. The first can be used for temporary files that may be discarded once the program has completed, and the second for files containing information relevant to the current user, such as saved output from the program. Unlike @dir$, these locations are guaranteed to be writable (in normal circumstances). @tmp$ and @usr$ are pre-defined, you must not assign new values to them.

VDU variables

The variable @vdu% allows you to access the values of a number of BASIC's internal text and graphics parameters. Some of the more useful of these are listed below; in general you should only read these values (they should be written using the associated BASIC statements).

The main VDU variables may alternatively be accessed via the @vdu{} structure, as shown below:

ValueAlternative Parameter
@vdu%!0@vdu.o.x%The horizontal graphics origin (in BBC BASIC graphics units), as set by ORIGIN or VDU 29.
@vdu%!4@vdu.o.y%The vertical graphics origin (in BBC BASIC graphics units), as set by ORIGIN or VDU 29.
@vdu%!8@vdu.l.x%The horizontal coordinate (in pixels) of the last point 'visited', as set by MOVE, PLOT etc.
@vdu%!12@vdu.l.y%The vertical coordinate (in pixels) of the last point 'visited', as set by MOVE, PLOT etc.
@vdu%!16@vdu.p.x%The horizontal coordinate (in pixels) of the previous point 'visited', as set by MOVE, PLOT etc.
@vdu%!20@vdu.p.y%The vertical coordinate (in pixels) of the previous point 'visited', as set by MOVE, PLOT etc.
@vdu%!24@vdu.tl%The left edge of the text viewport (in pixels), as set by VDU 28.
@vdu%!28@vdu.tr%The right edge of the text viewport (in pixels), as set by VDU 28.
@vdu%!32@vdu.tt%The top of the text viewport (in pixels), as set by VDU 28.
@vdu%!36 @vdu.tb%The bottom of the text viewport (in pixels), as set by VDU 28.
@vdu%!40@vdu.d.x%The width of a graphics 'dot', in pixels (LS 16 bits only).
@vdu%!44@vdu.d.y%The height of a graphics 'dot', in pixels (LS 16 bits only).
@vdu%!48@vdu.c.x%The horizontal text cursor position, in pixels.
@vdu%!52@vdu.c.y%The vertical text cursor position, in pixels.
@vdu%!56*@vdu.hf%The handle of the current font, as set by *FONT.
@vdu%!60*@vdu.hr%The handle of the current graphics clipping rectangle, as set by VDU 24 .
@vdu%?64@vdu.g.a&The current graphics foreground action (or ROP2 code), as set by GCOL.
@vdu%?65@vdu.g.b&The current graphics foreground colour, as set by GCOL.
@vdu%?66@vdu.g.c&The current graphics background action (or ROP2 code), as set by GCOL.
@vdu%?67@vdu.g.d&The current graphics background colour, as set by GCOL.
@vdu%?68@vdu.t.a&The top line of the text cursor (caret), as set by VDU 23,0,10.
@vdu%?69@vdu.t.b&The bottom line of the text cursor (caret), as set by VDU 23,0,11.
@vdu%?70@vdu.t.c&The text foreground colour, as set by COLOUR.
@vdu%?71@vdu.t.d&The text background colour, as set by COLOUR.
@vdu%?72@vdu.m.a&The current MODE number (255 for a user-defined mode).
@vdu%?73@vdu.m.b&The colour mask for the current MODE (number of colours−1).
@vdu%?74@vdu.m.c&The VDU emulator flags byte (8 bits).
@vdu%?75@vdu.m.d&The scroll counter in VDU 14 paged mode.
@vdu%?76@vdu.w.a&The width, in pixels, of the text cursor (caret) as set by VDU 23,0,18.
@vdu%?77@vdu.w.b&The thickness, in pixels, of solid straight lines and outline shapes, as set by VDU 23,23....
@vdu%?78@vdu.w.c&The text cursor (caret) movement control, as set by VDU 23,16....
@vdu%?79@vdu.w.d&BBC BASIC for Windows: Set to &80 to suppress character substitution in MODE 7.
BBC BASIC for SDL 2.0: The inter-character spacing adjustment, in pixels.
 
* 32-bit editions only. In 64-bit editions these are 64-bit values, use @vdu.hf% and @vdu.hr%.
 
@vdu%!208@size.x%The width, in pixels, of BASIC's output 'screen', as set by MODE or VDU 23,22.
@vdu%!212@size.y%The height, in pixels, of BASIC's output 'screen', as set by MODE or VDU 23,22.
@vdu%!216@char.x%The width, in pixels, of one character cell, as set by MODE or VDU 23,22.
@vdu%!220@char.y%The height, in pixels, of one character cell, as set by MODE or VDU 23,22.
 
These apply to BBC BASIC for Windows only, BBC BASIC for SDL 2.0 does not currently support hardcopy output:
 
@vdu%!224The width of a character cell on the printer, as set by *PRINTERFONT.
@vdu%!228The height of a character cell on the printer, as set by *PRINTERFONT.
@vdu%!232The left edge of the printed page (left margin), as set by *MARGINS.
@vdu%!236The right edge of the printed page, as set by *MARGINS.
@vdu%!240The top edge of the printed page (top margin), as set by *MARGINS.
@vdu%!244The bottom edge of the printed page, as set by *MARGINS.
 
@vdu%!-12 The horizontal coordinate determining where text will appear on the page when sent to the printer.
@vdu%!-8The vertical coordinate determining where text will appear on the page when sent to the printer.
@vdu%!-4The current printer job ID (if any).

In BBC BASIC for SDL 2.0 System Variables may be 'stale', that is they may not represent the current state unless a 'thread synchronisation' is first performed. This is most easily (and quickly) achieved by reading the POS or VPOS value:

IF POS REM. SDL thread sync
REM. System Variables may now be read reliably

Creation of variables

Variables can be created in a number of ways:


Structures

Structures are quite similar to arrays; both structures and arrays provide a means of grouping together several related variables. Whereas an array contains a number of elements of the same type, each identified by a numeric subscript (or subscripts), a structure contains a number of members (possibly) of different types, each identified by a name.

For example suppose you want to represent the position of an object in 3D space, which you can define by means of three numeric coordinates X, Y and Z. You could simply use three scalar variables, but it might be clearer or more convenient to group them together as a single entity describing the object position. One way of doing that would be to define an array with three elements:

DIM objpos(2)
objpos(0) = X
objpos(1) = Y
objpos(2) = Z
An alternative way would be to define a structure with three members:
DIM objpos{x,y,z}
objpos.x = X
objpos.y = Y
objpos.z = Z
In this case there is not much to choose between the alternatives, although the structure method does have the advantage of indicating the function of each member. However if the object description includes a name as well as a position then the array method cannot be used, because you can't store both a string and a number in the same array. In that case a structure is the answer:
DIM object{name$,pos{x,y,z}}
object.name$ = Name$
object.pos.x = X
object.pos.y = Y
object.pos.z = Z

Structure members

Structure members can be any of the normal variable types: variant numeric (having no suffix character), double numeric (indicated by a # suffix), integer numeric (indicated by a % or %% suffix), byte numeric (indicated by a & suffix), string (indicated by a $ suffix), array (variant numeric, double, integer, byte or string) or a sub-structure. A structure member is referenced by the name of the structure followed by a dot then the name of the member. With nested structures each sub-structure is separated by another dot:
structure_name.member_name
structure.substructure.subsubstructure.member
Normally a structure member can be treated in exactly the same way as a simple variable of the same type. However this is not so in the case of operations on entire arrays (e.g. SUM) or entire structures (e.g. SWAP) which cannot be used with structure members. Similarly you cannot pass an array member or a sub-structure member as the parameter of a procedure or function.

A byte-array structure member is treated as a special case. The construct structure.array&() is exactly equivalent to $$^structure.array&(0), that is it refers to a NUL-terminated string stored in the array. This is convenient for certain structures used by the Windows™ and SDL 2.0 APIs.

On 64-bit platforms (only) a structure member declared as member%% is aliased to member% (although still 64-bits) and can be accessed as either. This is to simplify accessing pointers within structures, where the pointer is declared as a 32-bit member on 32-bit platforms and as a 64-bit member on 64-bit platforms, without needing conditional code. For this reason it is best to avoid having both a 32-bit and a 64-bit member with the same name.

Declaring a structure

Structures, like arrays, are declared with DIM:
DIM mystruct{one,two%,three$,four(3),five%(1,2),six$(9),seven{a,b%}}
This statement declares a structure called mystruct which contains the following members:
mystruct.oneA variant numeric value
mystruct.two%An integer value
mystruct.three$A string
mystruct.four()An array of four variant numeric values
mystruct.five%()An array of six integer values (2 rows and 3 columns)
mystruct.six$()An array of ten strings
mystruct.seven{}A sub-structure containing the members mystruct.seven.a and mystruct.seven.b%
Note the use of brace characters ({ }) rather than parentheses (( )) which are used when declaring an array.

Arrays of structures

As well as single structures, you can have arrays of multiple structures with the same format (i.e. each structure in the array has an identical set of members, but the values of the members are different for each array element). Arrays of structures are declared as follows:
DIM mystruct{(10)one,two%,three$}
or
DIM item{one,two%,three$}
DIM mystruct{(10)} = item{}
These declare an array of 11 structures (subscript 0 to subscript 10) each of which has three members. Individual members are referenced as follows:
mystruct{(1)}.one
mystruct{(5)}.two%
mystruct{(10)}.three$
Note that it is necessary to use both braces and parentheses to indicate to BASIC that you are referring to an element of an array of structures, rather than a simple array element. Arrays of structures can be multi-dimensional.

Structure prototypes

As well as the methods given in the previous two sections, you can declare structures, sub-structures and arrays of structures based on a previously declared structure. This is very useful in enabling you to declare several structures with identical layouts, or in using predefined structure declarations, for example in 'header' files executed using CALL.

The syntax is as follows:

DIM new_struct{} = proto_struct{}
DIM nest_struct{name$,pos{} = proto_struct{}}
DIM struct_array{(1,2)} = proto_struct{}
The first example declares a structure new_struct{} which has the same layout and member names as the structure proto_struct{}. The second example declares a structure nest_struct{} whose second member pos is a sub-structure with the same layout and member names as the structure proto_struct{}. The third example declares an array (with 2 rows and 3 columns) of structures, each element of which has the same layout and member names as proto_struct{}.

Note: You cannot use a LOCAL structure as a prototype for another structure.

Passing structures to procedures and functions

You can pass structure members to procedures and functions exactly as you would simple variables of the same type (with the exception of array and sub-structure members). However you can also pass entire structures to procedures and functions:
DIM objpos{x,y,z}
PRINT FN_distance(objpos{})
END

DEF FN_distance(s{})
= SQR(s.x^2 + s.y^2 + s.z^2)
Here the function FN_distance has been defined with a formal structure parameter s{} and has been called with the actual structure parameter objpos{}; note the use of opening and closing braces with nothing in between. In just the same way as passing an entire array to a procedure or function the parameter is passed by reference so any changes that take place to structure members within the procedure or function affect the values of those members on return. See the section on Procedures and functions for more details.

You can also pass entire arrays of structures to procedures and functions:

DIM buffer{(1000) signal%, timestamp%}
PROCclear(buffer{()}, 0, 1000)

DEF PROCclear(B{()}, start%, finish%)
LOCAL I%
FOR I%=start% TO finish%
  B{(I%)}.signal% = 0
  B{(I%)}.timestamp% = 0
NEXT
ENDPROC

Be careful when passing structures to procedures or functions in libraries. If you crunch your main program with the Abbreviate names option, the names of the structure members will no longer agree with the names they have in the library, unless you use the crunch embedded files option.

LOCAL and PRIVATE structures

You can declare structures which are LOCAL or PRIVATE to a procedure or function. You do that using an equivalent syntax to that used for arrays:
DEF PROCtest1
LOCAL locstruct{}
DIM locstruct{a,b,c}

DEF FNtest2
PRIVATE pristruct{}
DIM pristruct{p%,q%,r%}
All members of a LOCAL structure are initialised to zero or an empty string. Members of a PRIVATE structure are initially zero or empty strings, but subsequently keep their values from one call of the procedure/function to the next.

Returning structures from procedures and functions

It is possible to declare a structure within a function or procedure and return that new structure to the calling code. To do that you must pass an undeclared structure as the parameter and use the RETURN keyword in the function or procedure definition:
PROCnewstruct(alpha{})
PROCnewstruct(beta{})
PROCnewstruct(gamma{})

DEF PROCnewstruct(RETURN s{})
DIM s{one,two%,three$}
...
ENDPROC
If you pass a structure which already exists, it must have an identical format to the declaration within the function or procedure.

Note: You cannot use this technique to declare LOCAL structures.

Using structures with the Windows and SDL 2.0 APIs

Structures can often be useful when passing parameters to Windows™ or SDL 2.0 Application Program Interface functions. For example the ClientToScreen API function uses a structure containing the X and Y coordinates of a point on the screen:
DIM pt{x%,y%}
pt.x% = clientX%
pt.y% = clientY%
SYS "ClientToScreen", @hwnd%, pt{}
screenX% = pt.x%
screenY% = pt.y%
In this case pt{} evaluates to the address of the structure in memory, so pt.x% is the same as pt{}!0 and pt.y% is the same as pt{}!4.

In the case of a sub-structure the value returned is the offset from the start of its parent structure, so to discover the memory address of a sub-structure you must add the two together:

DIM window{hw%,rc{l%,t%,r%,b%}}
window.hw% = @hwnd%
SYS "GetWindowRect", window.hw%, window{}+window.rc{}
If you need to create a structure member of an arbitrary size you can use a byte array. For example the Windows™ OSVERSIONINFO structure consists of five DWORDs (32-bit integers) followed by a 128-byte character array:
DIM osvi{Size%,Major%,Minor%,Build%,Platform%,ServicePack&(127)}
The address of the ServicePack member can be obtained as osvi{}+20 or as ^osvi.ServicePack&(0). Its value as a NUL-terminated string can be obtained as osvi.ServicePack&().

User-defined data types

Structures can be useful for creating data types which are not supported natively in BBC BASIC for Windows or BBC BASIC for SDL 2.0. For example BBC BASIC doesn't have a 16-bit (word) data type, but it is possible to create something similar using a structure containing two byte members:
DIM word{l&,h&}
Here the structure word{} consists of the least-significant byte word.l& and the most-significant byte word.h&. You could write a set of procedures for manipulating 16-bit values stored in such a structure.

Finding the size of a structure

You can discover the size (in bytes) of a structure or sub-structure as follows:
size% = DIM(struct_name{})
This can be particularly useful for some structures used by the Windows™ and SDL 2.0 APIs which require the first member to contain the size of the structure:
DIM osvi{} = _OSVERSIONINFO{}
osvi.Size% = DIM(osvi{})

Copying a structure

You can copy the contents of one structure into another structure as follows:
dest{} = source{}
Note that BASIC only checks that the sizes of the structures are the same, not that their contents are compatible. Do not copy structures containing string members; since only the string descriptor is copied, not the string itself, you are likely to confuse BASIC and may even crash it.

Assigning a pointer to a structure

Sometimes an OS API function returns a pointer to a structure, in which case you may need to assign that pointer to a structure you have already created. You can do that as follows:
DIM canvas{f%, p%, w%, h%, s%, p%, u%, l%, d%, X%, Y%, W%, H%, b%, r%}
SYS "SDL_CreateRGBSurface", 0, W%, H%, 32, R%, G%, B%, A% TO canvas%%
PTR(canvas{}) = canvas%%
Another occasion when you may wish to assign a pointer is when you are using structures to implement a linked list. In that case you are likely to want to reserve a block of memory to contain the new node:
DIM node{link, data...} : REM Define a link node
DIM new%% DIM(node{})   : REM Allocate memory for a new mode
PTR(node{}) = new%%     : REM Point the structure to the new node
Note that the resulting structure is not guaranteed to be aligned.


Program flow control

Introduction

Whenever BBC BASIC comes across a FOR, REPEAT, WHILE, GOSUB, FN or PROC statement, it needs to remember where it is in the program so that it can loop back or return there when it encounters a NEXT, UNTIL, ENDWHILE or RETURN statement or when it reaches the end of a function or procedure. These 'return addresses' tell BBC BASIC where it is in the structure of your program.

Every time BBC BASIC encounters a FOR, REPEAT, WHILE, GOSUB, FN or PROC statement it 'pushes' the return address onto a 'stack' and every time it encounters a NEXT, UNTIL, ENDWHILE or RETURN statement, or the end of a function or procedure, it 'pops' the latest return address off the stack and goes back there.

Apart from memory size, there is no limit to the level of nesting of FOR ... NEXT, REPEAT ... UNTIL, WHILE ... ENDWHILE and GOSUB ... RETURN operations. The untrappable error message 'No room' will be issued if all the stack space is used up.

Program structure limitations

The use of a common stack has one disadvantage (if it is a disadvantage) in that it forces strict adherence to proper program structure. It is not good practice to exit from a FOR ... NEXT loop without passing through the NEXT statement: it makes the program more difficult to understand and the FOR address is left on the stack. Similarly, the loop or return address is left on the stack if a REPEAT ... UNTIL loop or a GOSUB ... RETURN structure is incorrectly exited. This means that if you leave a FOR..NEXT loop without executing the NEXT statement, and then subsequently encounter, for example, an ENDWHILE statement, BBC BASIC will report an error (in this case a 'Not in a WHILE loop' error). The example below would result in the error message 'Not in a REPEAT loop at line 460'.
400 REPEAT
410   INPUT ' "What number should I stop at", num
420   FOR i=1 TO 100
430     PRINT i;
440     IF i=num THEN 460
450   NEXT i
460 UNTIL num=-1

Leaving program loops

There are a number of ways to leave a program loop which do not conflict with the need to write tidy program structures. These are discussed below.

REPEAT ... UNTIL loops

One way to overcome the problem of exiting a FOR ... NEXT loop is to restructure it as a REPEAT ... UNTIL loop. The example below performs the same function as the previous example, but exits the structure properly. It has the additional advantage of more clearly showing the conditions which will cause the loop to be terminated (and does not require line numbers):
REPEAT
  INPUT ' "What number should I stop at", num
  i=0
  REPEAT
    i=i+1
    PRINT i;
  UNTIL i=100 OR i=num
UNTIL num=-1

Changing the loop variable

Another way of forcing a premature exit from a FOR ... NEXT loop is to set the loop variable to a value equal to the limit value. Alternatively, you could set the loop variable to a value greater than the limit (assuming a positive step), but in this case the value on exit would be different depending on why the loop was terminated (in some circumstances, this might be an advantage). The example below uses this method to exit from the loop:
REPEAT
  INPUT ' "What number should I stop at", num
  FOR i=1 TO 100
    PRINT i;
    IF i=num THEN
      i=500
    ELSE
      REM More program here if necessary
    ENDIF
  NEXT
UNTIL num=-1

Using the EXIT statement

You can use the EXIT FOR statement to achieve the same effect as the first example, without requiring a GOTO:
REPEAT
  INPUT ' "What number should I stop at", num
  FOR i=1 TO 100
    PRINT i;
    IF i=num THEN EXIT FOR
    REM More program here if necessary
  NEXT i
UNTIL num=-1

Moving the loop into a procedure

A radically different approach is to move the loop into a user-defined procedure or function. This allows you to jump out of the loop directly:
DEF PROCloop(num)
LOCAL i
FOR i=1 TO 100
  PRINT i;
  IF i=num THEN ENDPROC
  REM More program here if necessary
NEXT i
ENDPROC
If you needed to know whether the loop had been terminated prematurely you could return a different value:
DEF FNloop(num)
LOCAL i
FOR i=1 TO 100
  PRINT i;
  IF i=num THEN =TRUE
  REM More program here if necessary
NEXT i
=FALSE

Local arrays

Since local variables are also stored on the processor's stack, you cannot use a FOR ... NEXT loop to make an array LOCAL. For example, the following program will give the error message 'Not in a FN or PROC':
DEF PROC_error_demo
FOR i=0 TO 10
  LOCAL data(i)
NEXT
ENDPROC
Fortunately BBC BASIC allows you to make an entire array 'local' as follows:
DEF PROC_error_demo
LOCAL data()
DIM data(10)
ENDPROC
Note the use of an opening and closing bracket with nothing in between.


Indirection

Introduction

Many versions of BASIC allow access to the computer's memory with the PEEK function and the POKE statement. Such access, which is limited to one byte at a time, is sufficient for setting and reading screen locations or 'flags', but it is difficult to use for building more complicated data structures. The indirection operators provided in BBC BASIC enable you to read and write to memory in a far more flexible way. They provide a simple equivalent of PEEK and POKE, but they come into their own when used to build complicated data structures, pass data to or from Windows or SDL 2.0 API functions or for use with assembly language programs.

The indirection operators should be used with great care, since attempting to read from or write to an inappropriate address may well crash BBC BASIC. Generally, the address used should either be in an area of memory allocated with DIM or have been returned from a Windows or SDL 2.0 API function.

There are five indirection operators:

NameSymbolData type No. of bytes
Query?Byte 1
Pling!Integer (32-bit) 4
Bracket]Integer (64-bit) 8
Pipe|Float 5, 8 or 10
Dollar$, $$String 1 to 65536

The ? operator

The query operator accesses individual bytes of memory. ?M means 'the contents of' memory location 'M'. The first example below writes &23 to memory location M, the second example sets 'number' to the contents of that memory location and the third example prints the contents of that memory location.
DIM M 0
?M=&23
number=?M
PRINT ?M
Thus, '?' provides a direct replacement for PEEK and POKE.
?A=B is equivalent to POKE A,B
B=?A is equivalent to B=PEEK(A)
The range of values which may be stored is 0 to 255 (i.e. byte indirection is unsigned).

Query as a byte variable

A byte variable, '?count' for instance, may be used as the control variable in a FOR ... NEXT loop and only one byte of memory will be used.
DIM count% 0
FOR ?count%=0 TO 20
  - - -

  - - -
NEXT

The ! operator

The pling (!) indirection operator accesses 4 bytes (32-bits) of memory. Thus,
DIM M 3
!M=&12345678
would load
&78 into address M
&56 into address M+1
&34 into address M+2
&12 into address M+3
and
PRINT ~!M  
would give
12345678
The range of values which may be stored is −2147483648 to +2147483647 (i.e. integer indirection is signed).

The ] operator

The bracket (]) indirection operator accesses 8 bytes (64-bits) of memory. Thus,
*HEX 64
DIM M 7
]M=&123456789ABCDEF0
would load
&F0 into address M
&DE into address M+1
&BC into address M+2
&9A into address M+3
&78 into address M+4
&56 into address M+5
&34 into address M+6
&12 into address M+7
and
*HEX 64
PRINT ~]M
would give
123456789ABCDEF0
The range of values which may be stored is −9223372036854775808 to +9223372036854775807.

The | operator

The pipe (|) indirection operator accesses a floating-point value, which occupies 5 bytes (in *FLOAT 40 mode), 8 bytes (in *FLOAT 64 mode) or 10-bytes (in *FLOAT 80 mode) of memory. Thus,
|F% = PI
would load the floating-point representation of PI into addresses F% to F%+4, addresses F% to F%+7 or addresses F% to F%+9 depending on the *FLOAT mode in effect.

The $ operator

The string indirection operator ($) writes a string followed by a carriage-return into memory starting at the specified address. Do not confuse M$ with $M; the former is the familiar string variable whilst the latter means 'the string starting at memory location M'. For example,
$M="ABCDEF"
would load the ASCII characters A to F into addresses M to M+5 and &0D into address M+6, and
PRINT $M
would print
ABCDEF

The $$ operator

The indirection operator $$ operates like $ except that the string is terminated with NUL (&00) rather than carriage return (&0D). It is particularly useful when reading or writing strings through the Windows™ or SDL 2.0 API. For example:
SYS "GetCommandLine" TO cmdline%
cmdline$ = $$cmdline%

Use as binary (dyadic) operators

All the indirection examples so far have used only one operand. Provided the left-hand operand is a variable (such as 'memory') and not a constant, '?' and '!' can also be used as dyadic operators (in other words, they can be used with two operands). For instance, M?3 means 'the contents of memory location (M + 3)' and M!3 means 'the contents of the 4 bytes starting at (M + 3)'. In the following example, the contents of location (memory + 5) is first set to &50 and then printed:
memory?5=&50
PRINT memory?5
Thus,
A?I=B is equivalent to POKE A+I,B
B=A?I is equivalent to B=PEEK(A+I)
The example below displays the contents of the first 13 bytes of the current BASIC program:
memory=PAGE
FOR offset=0 TO 12
  PRINT ~memory+offset, ~memory?offset
NEXT
The memory address and the contents are printed in hexadecimal format.

Power of indirection operators

Indirection operators can be used to create special data structures, and as such they are an extremely powerful feature. For example, a structure consisting of a 10 character string, an 8 bit number and a reference to a similar structure can be constructed.

If M is the address of the start of the structure then:

$M    is the string
M?11  is the 8 bit number
M!12  is the address of the related structure
Linked lists and tree structures can easily be created and manipulated in memory using this facility.

The ^ operator

You can discover the memory address at which a variable is stored using the 'address of' operator ^. Once you know its address you can access the value of a variable by means of the appropriate indirection operator:
A% = 1234
PRINT !^A%
This will work for all types of variable (integer, floating-point, string, array etc.) but in the case of a normal string variable the address returned is not that of the first character of the string but of the 6-byte or 8-byte string descriptor (see the CALL statement for details of string descriptors). Therefore the address of the string itself is !^string$ or alternatively PTR(string$).

In the case of an array the address returned by ^array() is that of a pointer to the array parameter block, therefore the address of the parameter block is !^array(). To obtain the address of the array data you should specify the name of the first element, e.g. ^array(0).

Knowing the memory address of a variable may seem to be of little value but can be useful in special circumstances, particularly when calling Windows™ or SDL 2.0 API functions or in assembly language code. For example the ReadFile function requires you to pass the address of a variable in which the number of bytes read will be returned:

SYS "ReadFile", @hfile%(file%), store%, size%, ^bytesread%, 0
In assembly language code you might want to copy the value of a BBC BASIC (integer) variable into one of the processor's registers:
mov eax,[^variable%]
Note that the static variables A% to Z% occupy consecutive locations in memory, which can be handy when an API function requires the address of a structure rather than a simple variable. For example GetCursorPos requires the address of a POINT structure in which it returns two values:
SYS "GetCursorPos", ^X%
This will return the horizontal coordinate of the cursor in X% and the vertical coordinate in Y%.

Another use is to alter the byte-order of a variable, for example from little-endian to big-endian. The following code segment reverses the byte-order of the value stored in A%:

SWAP ?(^A%+0), ?(^A%+3)
SWAP ?(^A%+1), ?(^A%+2)

Operators and special symbols

The following list is a summary of the meaning of the various operators and special symbols used by BBC BASIC for Windows and BBC BASIC for SDL 2.0:

? An operator giving 8 bit indirection.
! An operator giving 32 bit indirection.
] An operator giving 64 bit indirection (when used alone).
" A delimiting character for constant strings. Strings always have an even number of " in them. " may be introduced into a string by the escape convention "".
# As a prefix indicates a file channel number (and is not optional). As a suffix indicates a 64-bit double variable.
$ As a prefix indicates a 'fixed string' (the syntax $<expression> is used to position a string anywhere in memory, overriding the interpreter's space allocation). As a suffix on a variable name, indicates a string variable. $$ indicates a NUL-terminated string.
% As a prefix indicates a binary constant e.g. %11101111. As a suffix on a variable name, indicates an integer (signed 32-bit) variable; %% indicates a signed 64-bit variable.
& As a prefix indicates a hexadecimal constant e.g. &EF. As a suffix on a variable name, indicates a byte (unsigned 8-bit) variable.
' A character which causes new lines in PRINT or INPUT.
( ) As a suffix on a variable name indicates an array. Objects in parentheses have highest priority. Delimiter for line labels.
{ } Braces indicate a structure.
= 'Becomes' for LET statement and FOR; 'result is' for FN; relation of equal to.
Negation and subtraction operator.
* Multiplication operator; prefix indicating operating system command (e.g. *DIR).
: Multi-statement line statement delimiter.
; Suppresses forthcoming action in PRINT, BPUT#, THEN and *RUN. Comment delimiter in the assembler. Delimiter in VDU and INPUT.
+ Unary plus and addition operator; concatenation between strings.
, Delimiter in lists.
. Decimal point in real constants; abbreviation symbol on keyword entry; introduce label in assembler; dot product of arrays; delimiter of structure members.
< Relation of less than.
<< Left-shift operator (signed or unsigned).
<<< Left-shift operator (always 64-bits, irrespective of the *HEX mode).
> Relation of greater than.
>> Right-shift operator (signed) (always 64-bits, irrespective of the *HEX mode).
>>> Right-shift operator (unsigned).
/ Division operator.
\ The line continuation character.
@ Prefix character for system variables.
<= Relation of less than or equal to.
>= Relation of greater than or equal to.
<> Relation of not equal to.
== Relation of equal to (alternative to =).
[ ] Delimiters for assembler statements. Statements between these delimiters may need to be assembled twice in order to resolve any forward references. The pseudo operation OPT (initially 3) controls errors and listing.
^ Exponentiation (raise-to-the-power-of) operator; the address of operator.
~ A character in the start of a print field indicating that the item is to be printed in hexadecimal. Also used with STR$ to cause conversion to a hexadecimal string.
| A delimiter in the VDU statement. A unary operator giving floating-point indirection.
+= Assignment with addition (A += B is equivalent to A = A + B)
−= Assignment with subtraction (A −= B is equivalent to A = A − B)
*= Assignment with multiplication (A *= B is equivalent to A = A * B)
/= Assignment with division (A /= B is equivalent to A = A / B)
AND= Assignment with AND (A AND= B is equivalent to A = A AND B)
DIV= Assignment with DIV (A DIV= B is equivalent to A = A DIV B)
EOR= Assignment with EOR (A EOR= B is equivalent to A = A EOR B)
MOD= Assignment with MOD (A MOD= B is equivalent to A = A MOD B)
OR= Assignment with OR (A OR= B is equivalent to A = A OR B)


Keywords

It is not always essential to separate keywords with spaces, for example DEGASN1 is equivalent to DEG ASN 1: both the DEG and the ASN are recognized as keywords. However ADEGASN1 is treated as a variable name: neither DEG nor ASN is recognized as a keyword. Nevertheless adding spaces between keywords makes your program easier to read, and will not impair performance so long as the program is compiled using the discard spaces crunch option.

In general variable names cannot start with a keyword, but there are some exceptions. For example the constant PI and the pseudo-variables LOMEM, HIMEM, PAGE, and TIME can form the first part of the name of a variable. Therefore PILE and TIMER are valid variable names although PI$ and TIME% are not.

37 out of the total of 138 keywords are allowed at the start of a variable name. They are shown in bold type in the table below:

Keywords Available

ABS ACS ADVAL AND ASC ASN
ATN BGET BPUT BY CALL CASE
CHAIN CHR$ CIRCLE CLEAR CLG CLOSE
CLS COLOR COLOUR COS COUNT DATA
DEF DEG DIM DIV DRAW ELLIPSE
ELSE END ENDCASE ENDIF ENDPROC ENDWHILE
ENVELOPE EOF EOR ERL ERR ERROR
EVAL EXIT EXP EXT FALSE FILL
FN FOR GCOL GET GET$ GOSUB
GOTO HIMEM IF INKEY INKEY$ INPUT
INSTALL INSTR( INT LEFT$( LEN LET
LINE LN LOCAL LOG LOMEM MID$(
MOD MODE MOUSE MOVE NEXT NOT
OF OFF ON OPENIN OPENOUT OPENUP
OR ORIGIN OSCLI OTHERWISE PAGE PI
PLOT POINT( POS PRINT PRIVATE PROC
PTR QUIT RAD READ RECTANGLE REM
REPEAT REPORT RESTORE RETURN RIGHT$( RND
RUN SGN SIN SOUND SPC SQR
STEP STOP STR$ STRING$( SUM SWAP
SYS TAB( TAN THEN TIME TINT
TO TRACE TRUE UNTIL USR VAL
VDU VPOS WAIT WHEN WHILE WIDTH


Debugging

"If debugging is the process of removing bugs, then programming must be the process of putting them in."

It is inevitable that programs will sometimes contain bugs. You may be able to write small programs which are error-free and work first time, but the larger your programs get the greater the likelihood that they will have errors. These fall into three categories:

BBC BASIC for Windows and BBC BASIC for SDL 2.0 incorporate a number of features to make it easier to find and fix bugs.

Syntax colouring

By default the syntactical elements of your program (keywords, strings, comments etc.) are shown in different colours in the program editor. This allows you, at a glance, to spot some errors which would cause your program to fail, for example:

Indentation

By default looping and nesting constructs in your program are indented by the program editor. This allows you to quickly spot errors such as:

Error reporting

If the interpreter detects an error condition whilst your program is running it will activate the built-in error handling mechanism. In addition, when not in trace mode, it will automatically scroll to and highlight the statement that triggered the error (unless it is in a CALLed or INSTALLed module); this happens even if the error is trapped using ON ERROR.

If you hover the mouse over the highlighted statement, the error message will be displayed alongside in a tooltip. The statement remains highlighted so long as your program's output window remains open, or until you enter a command in immediate mode.

Fast edit-run cycle

As BBC BASIC is an interpreted language (albeit a very fast one) it has the advantage of allowing you to edit and re-run a program instantly. After you have made a change to a program you don't have to wait for it to be re-compiled and re-linked before you can run it. Although trial-and-error debugging isn't to be encouraged, there are occasions when it is the most efficient way of solving a problem.

Run-time monitoring

You can monitor the execution of your program in two main ways: using the Trace facility and the List Variables facility.

In Trace mode, the program statement which is currently being executed is highlighted, and this highlight moves as your program is running (unless the statement currently being executed is in a CALLed or INSTALLed module). If your program gets stuck in an 'infinite loop', you will be able to see what part of the program is being executed. If your program stops running because of an error (or an END or STOP statement) the last statement executed will remain highlighted for as long as the program's output window remains open, or until you enter a command in immediate mode.

If List Variables is enabled a window opens in which the values of the currently-defined variables are displayed (along with a list of arrays and function and procedure names). This list is updated as your program runs so you can monitor how the values change and possibly spot anomalous values. Note that variables are only listed once they have been accessed by your program (except for the static integer variables which are always listed).

Single-step execution

If you know (or suspect) the region of your program in which an error is occuring, but it happens too quickly to spot using Trace or List Variables, then you can step through that region of the program one statement at a time. You can execute the program at full-speed until just before the section of interest (using Run to cursor) then single step execution whilst monitoring variable values and output.

Immediate mode

If an untrapped error occurs at run time the program exits to immediate mode and displays an error message followed by the > prompt. At the prompt you can type any valid BASIC statement then press the Enter key, whereupon (as the name suggests) the statement will be executed immediately. This can be very useful in ascertaining the values of, or performing calculations on, variables; issuing 'star' commands (for example you might want to change the current directory with *CD) or even calling procedures and functions.


Error handling

Default error handling

By default, if BBC BASIC detects an error while running a program it will immediately halt execution and report an error message, the filename of the INSTALLed or CALLed module in which the error occurred (if appropriate) and the line number (if any). This behaviour may often be adequate, but you have the option of trapping errors and writing your own code to handle them.

Reasons for trapping errors

There are a number of reasons why you might want to trap errors:

To make your program more 'friendly'

When you write a program for yourself, you know what you want it to do and you also know what it can't do. If, by accident, you try to make it do something which could give rise to an error, you accept the fact that BBC BASIC might terminate the program and return to immediate mode. However, when somebody else uses your program they are not blessed with your insight and they may find the program 'crashing out' without knowing what they have done wrong. Such programs are called 'fragile'. You can protect your user from much frustration if you display a more 'friendly' error report and fail 'gracefully'.

To ensure the error message is visible

The default error reporting writes the error message to the screen just like any other text output. Unfortunately there are several reasons why your program might not display a useful message, for example:

To allow clearup operations to take place

There can be situations, particularly when using the Windows or SDL 2.0 API, where aborting an operation 'mid stream' leaves the system in an unstable state. For example it might result in your program failing to work again unless you quit and restart BBC BASIC for Windows. It could even, in unusual circumstances, crash BBC BASIC. In such cases your own error handler can carry out the necessary tidying-up operations before reporting the error and exiting gracefully.

To allow execution to continue

Although it is preferable to prevent errors occurring in the first place, there can be occasions when this is difficult or impossible to achieve, and it is better to allow the error to happen. In these circumstances error trapping allows the program to continue execution, possibly with some specific action being taken as a result.

Error trapping commands

The two main commands provided by BBC BASIC for error trapping and recovery are:
ON ERROR ....
and
ON ERROR LOCAL ....

ON ERROR

The ON ERROR command directs BBC BASIC to execute the statement(s) following ON ERROR when a trappable error occurs:
ON ERROR PRINT '"Oh No!":END
If an error was detected in a program after this line had been encountered, the message 'Oh No!' would be printed and the program would terminate. If, as in this example, the ON ERROR line contains the END statement or transfers control elsewhere (e.g. using GOTO) then the position of the line within the program is unimportant so long as it is encountered before the error occurs. If there is no transfer of control, execution following the error continues as usual on the succeeding line, so in this case the position of the ON ERROR line can matter.

As explained in the Program Flow Control sub-section, every time BBC BASIC encounters a FOR, REPEAT, GOSUB, FN, PROC or WHILE statement it 'pushes' the return address on to a 'stack' and every time it encounters a NEXT, UNTIL, RETURN, ENDWHILE statement or the end of a function or procedure it 'pops' the latest return address of the stack and goes back there. The program stack is where BBC BASIC records where it is within the structure of your program.

When an error is detected by BBC BASIC, the stack is cleared. Thus, you cannot just take any necessary action depending on the error and return to where you were because BBC BASIC no longer knows where you were.

If an error occurs within a procedure or function, the value of any parameters and LOCAL variables will be the last value they were set to within the procedure or function which gave rise to the error (the values, if any, they had before entry to the procedure or function are lost).

If an error occurs within a procedure or function containing PRIVATE variables, all subsequent calls to that procedure or function will be treated by BASIC as if they were re-entrant and the PRIVATE statement will be ignored. It is therefore most important that, if your program attempts to recover from errors using ON ERROR, no errors are permitted to occur within a procedure or function containing PRIVATE variables. If necessary use *ESC OFF to ensure that not even the Escape error can occur, or use ON ERROR LOCAL and RESTORE LOCAL to restore the PRIVATE variables.

ON ERROR LOCAL

The ON ERROR LOCAL command prevents BBC BASIC clearing the program stack. By using this command, you can trap errors within a FOR ... NEXT, REPEAT ... UNTIL or WHILE ... ENDWHILE loop or a subroutine, function or procedure without BBC BASIC losing its place within the program structure.

The following example program will continue after the inevitable 'Division by zero' error:

FOR n=-5 TO 5
  ok% = TRUE 
  ON ERROR LOCAL PRINT "Infinity" : ok% = FALSE
  IF ok% PRINT "The reciprocal of ";n;" is ";1/n
NEXT n
This is, of course, a poor use of error trapping. You should test for n=0 rather than allow the error to occur. However, it does provide a simple demonstration of the action of ON ERROR LOCAL. Also, you should test ERR to ensure that the error was the one you expected rather than, for example, Escape (ERR is a function which returns the number of the last error; it is explained later in this sub-section).

After the loop terminates (when 'n' reaches 6) the previous error trapping state is restored. Alternatively, you can explicitly restore the previous state using RESTORE ERROR.

You can call a subroutine, function or procedure to 'process' the error, but it must return to the loop, subroutine, function or procedure where the error was trapped:

x=OPENOUT "TEST"
FOR i=-5 TO 5
  ok% = TRUE
  ON ERROR LOCAL PROCerr : ok% = FALSE
  IF ok% PRINT#x,1/i
NEXT
CLOSE#x
:
x=OPENIN "TEST"
REPEAT
  INPUT#x,i
  PRINT i
UNTIL EOF#x
CLOSE#x
END
:
DEF PROCerr
IF ERR <> 18 REPORT:END
PRINT#x,3.4E38
ENDPROC
The position of the ON ERROR LOCAL within a procedure or function can be important. Consider the following two examples:
DEF PROC1(A)
ON ERROR LOCAL REPORT : ENDPROC
LOCAL B
....
DEF PROC1(A)
LOCAL B
ON ERROR LOCAL REPORT : ENDPROC
....
In the case of the first example, if an error occurs within the procedure, the formal parameter A will be automatically restored to its original value, but the LOCAL variable B will not; it will retain whatever value it had when the error occurred. In the case of the second example, both the formal parameter A and the LOCAL variable B will be restored to the values they had before the procedure was called.

If a procedure or function uses PRIVATE variables it is essential that any ON ERROR LOCAL statement be placed after the PRIVATE statement.

ON ERROR or ON ERROR LOCAL?

If you use ON ERROR, BBC BASIC clears the program stack. Once your program has decided what to do about the error, it must re-enter the program at a point that is not within a loop, subroutine, function or procedure. In practice, this generally means somewhere towards the start of the program. This is often undesirable as it makes it difficult to build well structured programs which include error handling. On the other hand, you will probably only need to write one or two program sections to deal with errors.

At first sight ON ERROR LOCAL seems a more attractive proposition since BBC BASIC remembers where it is within the program structure. The one disadvantage of ON ERROR LOCAL is that you will need to include an error handling section at every level of your program where you need to trap errors. Many of these sections of program could be identical.

You can mix the use of ON ERROR and ON ERROR LOCAL within your program. A single ON ERROR statement can act as a 'catch all' for unexpected errors, and one or more ON ERROR LOCAL statements can be used when your program is able to recover from predictable errors. ON ERROR LOCAL 'remembers' the current ON ERROR setting, and restores it when the loop, procedure or function containing the ON ERROR LOCAL command is finished, or when a RESTORE ERROR or RESTORE LOCAL is executed.

Error reporting

There are three functions, ERR, ERL, REPORT$ and one statement, REPORT, which may be used to investigate and report on errors. Using these, you can trap out errors, check that you can deal with them and abort the program run if you cannot.

ERR

ERR returns the error number (see the section entitled Error messages and codes).

ERL

ERL returns the line number where the error occurred. If an error occurs in a procedure or function call, ERL will return the number of the calling line, not the number of the line in which the procedure/function is defined. If an error in a DATA statement causes a READ to fail, ERL will return the number of the line containing the DATA, not the number of the line containing the READ statement. If the line is not numbered, ERL returns zero.

REPORT$

REPORT$ returns the error string associated with the last error which occurred.

REPORT

REPORT prints out the error string associated with the last error which occurred. It is equivalent to PRINT REPORT$;

Error trapping examples

The majority of the following error trapping examples are all very simple programs without nested loops, subroutines, functions or procedures. Consequently, they use ON ERROR for error trapping in preference to ON ERROR LOCAL.

The example below does not try to deal with errors, it just uses ERR and REPORT to tell the user about the error. Its only advantage over BBC BASIC's normal error handling is that it gives the error number; it would probably not be used in practice. As you can see from the second run, pressing <ESC> is treated as an error (number 17).

ON ERROR PROCerror
REPEAT
  INPUT "Type a number " num
  PRINT num," ",SQR(num)
  PRINT
UNTIL FALSE
:
DEF PROCerror
PRINT
PRINT "Error No ";ERR
REPORT
END
Example run:
RUN
Type a number 1
         1         1
Type a number -2
        -2
Error No 21
Negative root

RUN Type a number <Esc> Error No 17 Escape
The example below has been further expanded to include error trapping. The only 'predictable' error is that the user will try a negative number. Any other error is unacceptable, so it is reported and the program aborted. Consequently, when <ESC> is used to abort the program, it is reported as an error. However, a further test for ERR=17 could be included so that the program would halt on ESCAPE without an error being reported.
ON ERROR PROCerror
REPEAT
 INPUT "Type a number " num
  PRINT num," ",SQR(num)
 PRINT
UNTIL FALSE
:
DEF PROCerror
PRINT
IF ERR=21 THEN PRINT "No negatives":ENDPROC
REPORT
END
This is a case where the placement of the ON ERROR statement is important. When PROCerror exits, execution continues after the ON ERROR statement, which in this case causes the program to restart from the beginning.

Example run:

RUN
Type a number 5
         5          2.23606798

Type a number 2 2 1.41421356
Type a number -1 -1 No negatives
Type a number 4 4 2
Type a number <Esc> Escape
The above example is very simple and was chosen for clarity. In practice, it would be better to test for a negative number before using SQR rather than trap the 'Negative root' error. A more realistic example is the evaluation of a user-supplied HEX number, where trapping 'Bad hex or binary' would be much easier than testing the input string beforehand.
ON ERROR PROCerror
REPEAT
  INPUT "Type a HEX number " Input$
  num=EVAL("&"+Input$)
  PRINT Input$,num
  PRINT
UNTIL FALSE
:
DEF PROCerror
PRINT
IF ERR=28 THEN PRINT "Not hex":ENDPROC
REPORT
END
The next example is similar to the previous one, but it uses the ON ERROR LOCAL command to trap the error.
REPEAT
  ON ERROR LOCAL PROCerror
  INPUT "Type a HEX number " Input$
  num=EVAL("&"+Input$)
  PRINT Input$,num
  PRINT
UNTIL FALSE
:
DEF PROCerror
PRINT
IF ERR=28 THEN PRINT "Not hex":ENDPROC
REPORT
END
Note that had ON ERROR (rather than ON ERROR LOCAL) been used in this case, an error would give rise to a Not in a REPEAT loop error at the UNTIL statement. This is because ON ERROR clears the program's stack.


Procedures and functions

Introduction

Procedures and functions are similar to subroutines in that they are 'bits' of program which perform a discrete function. Like subroutines, they can be performed (called) from several places in the program. However, they have two great advantages over subroutines: you can refer to them by name and the variables used within them can be made private to the procedure or function.

Arguably, the major advantage of procedures and functions is that they can be referred to by name. Consider the two similar program lines below.

IF name$="ZZ" THEN GOSUB 500 ELSE GOSUB 800

IF name$="ZZ" THEN PROC_end ELSE PROC_print
The first statement gives no indication of what the subroutines at lines 500 and 800 actually do. The second, however, tells you what to expect from the two procedures. This enhanced readability stems from the choice of meaningful names for the two procedures.

A function often carries out a number of actions, but it always produces a single result. For instance, the 'built in' function INT returns the integer part of its argument.

age=INT(months/12)
A procedure on the other hand, is specifically intended to carry out a number of actions, some of which may affect program variables, but it does not directly return a result.

Whilst BBC BASIC has a large number of pre-defined functions (INT and LEN for example) it is very useful to be able to define your own to do something special. Suppose you had written a function called FN_discount to calculate the discount price from the normal retail price. You could write something similar to the following example anywhere in your program where you wished this calculation to be carried out.

discount_price=FN_discount(retail_price)
It may seem hardly worth while defining a function to do something this simple. However, functions and procedures are not confined to single line definitions and they are very useful for improving the structure and readability of your program.

Names

The names of procedures and functions MUST start with PROC or FN and, like variable names, they cannot contain spaces. This restriction can give rise to some pretty unreadable names. However, the underline character can be used to advantage. Consider the procedure and function names below and decide which is the easier to read.
PROCPRINTDETAILS      FNDISCOUNT

PROC_print_details FN_discount
Function and procedure names may end with a '$'. However, this is not compulsory for functions which return strings.

Function and procedure definitions

Starting a definition

Function and procedure definitions are 'signalled' to BBC BASIC by preceding the function or procedure name with the keyword DEF. DEF must be at the beginning of the line. If the computer encounters DEF during execution of the program, the rest of the line is ignored. Consequently, you can put single line definitions anywhere in your program.

If two or more functions or procedures have the same name (either deliberately or by mistake) the one which is executed is determined by the following rules:

The function/procedure body

The 'body' of a procedure or function must not be executed directly - it must be performed (called) by another part of the program. Since BBC BASIC only skips the rest of the line when it encounters DEF, there is a danger that the remaining lines of a multi-line definition might be executed directly. You can avoid this by putting multi-line definitions at the end of the main program text after the END statement. Procedures and functions do not need to be declared before they are used and there is no speed advantage to be gained by placing them at the start of the program.

Ending a definition

The end of a procedure definition is indicated by the keyword ENDPROC. The end of a function definition is signalled by using a statement which starts with an equals (=) sign. The function returns the value of the expression to the right of the equals sign.

Single line functions/procedures

For single line definitions, the start and end are signalled on the same line. The first example below defines a function which returns the average of two numbers. The second defines a procedure which clears from the current cursor position to the end of the line (on a 40 column screen):
DEF FN_average(A,B) = (A+B)/2
DEF PROC_clear:PRINT SPC(40-POS);:ENDPROC

Extending the language

You can define a whole library of procedures and functions and include them in your programs, or INSTALL them at run-time. By doing this you can effectively extend the scope of the language. For instance, BBC BASIC does not have a 'clear to end of screen' command. The example below is a procedure to clear to the end of 'screen' (BASIC's output window) with an 80 column by 25 row display (e.g. MODE 3). The three variables used (i%, x%, and y%) are declared as LOCAL to the procedure (see later).
DEF PROC_clear_to_end
LOCAL i%,x%,y%
x%=POS:y%=VPOS
REM If not already on the last line, print lines of 
REM spaces which will wrap around until the last line
IF y% < 24 FOR i%=y% TO 23:PRINT SPC(80);:NEXT
REM Print spaces to the end of the last line.
PRINT SPC(80-x%);
REM Return the cursor to its original position
PRINT TAB(x%,y%);
ENDPROC

Passing parameters

When you define a procedure or a function, you list the parameters to be passed to it in brackets. For instance, the discount example expected one parameter (the retail price) to be passed to it. You can write the definition to accept any number of parameters. For example, we may wish to pass both the retail price and the discount percentage. The function definition would then look something like this:
DEF FN_discnt(price,pcent)=price*(1-pcent/100)
In this case, to use the function we would need to pass two parameters.
retail_price=26.55
discount_price=FN_discount(retail_price,25)
or
price=26.55
discount=25
price=FN_discount(price,discount)
or
price=FN_discount(26.55,25)

Formal and actual parameters

The value of the first parameter in the call to the procedure or function is passed to the first variable named in the parameter list in the definition, the second to the second, and so on. This is termed 'passing by value' (see also passing parameters by reference). The parameters declared in the definition are called 'formal parameters' and the values passed in the statements which perform (call) the procedure or function are called 'actual parameters'. There must be as many actual parameters passed as there are formal parameters declared in the definition.
FOR I% = 1 TO 10
PROC_printit(I%) : REM I% is the Actual parameter
NEXT
END

DEF PROC_printit(num1) : REM num1 is the Formal parameter
PRINT num1
ENDPROC
The formal parameters must be variables, but the actual parameters may be variables, constants or expressions. When the actual parameter is a variable, it need not be (and usually won't be) the same as the variable used as the formal parameter. Formal parameters are automatically made local to the procedure or function.

You can pass a mix of string and numeric parameters to the procedure or function, and a function can return either a string or numeric value, irrespective of the type of parameters passed to it. However, you must make sure that the parameter types match up. The first example below is correct; the second would give rise to an 'Incorrect arguments' error message and the third would cause a 'Type mismatch' error to be reported.

Correct
PROC_printit(1,"FRED",2)
END
:
DEF PROC_printit(num1,name$,num2)
PRINT num1,name$,num2
ENDPROC
Incorrect arguments error
PROC_printit(1,"FRED",2,4)
END
:
DEF PROC_printit(num1,name$,num2)
PRINT num1,name$,num2
ENDPROC
Type mismatch error
PROC_printit(1,"FRED","JIM")
END
:
DEF PROC_printit(num1,name$,num2)
PRINT num1,name$,num2
ENDPROC

Local variables

You can use the statements LOCAL and PRIVATE to declare variables for use locally within individual procedures or functions (the formal parameters are also automatically local to the procedure or function declaring them). Changing the values of local variables has no effect on variables of the same name (if any) elsewhere in the program. This technique is used extensively in the example file handling programs in this manual.

Declaring variables as LOCAL initialises them to zero (in the case of numeric variables) or null/empty (in the case of string variables). Declaring variables as PRIVATE causes them to retain their values from one call of the function/procedure to the next. If an array or structure is made LOCAL or PRIVATE it must be re-DIMensioned before use.

Variables which are not formal parameters nor declared as LOCAL or PRIVATE are known to the whole program, including all the procedures and functions. Such variables are called global.

Recursive functions/procedures

Because the formal parameters which receive the passed parameters are local, all procedures and functions can be recursive. That is, they can call themselves. But for this feature, the short example program below would be difficult to code. It is the often used example of a factorial number routine (the factorial of a number n is n * n−1 * n−2 * .... * 1. Factorial 6, for instance, is 6 * 5 * 4 * 3 * 2 * 1 = 720).
REPEAT
  INPUT "Enter an INTEGER less than 35 "num
UNTIL INT(num)=num AND num<35
fact=FN_fact_num(num)
PRINT num,fact
END
:
DEF FN_fact_num(n)
IF n=1 OR n=0 THEN =1
REM Return with 1 if n= 0 or 1
=n*FN_fact_num(n-1)
REM Else go round again
Since 'n' is the input variable to the function FN_fact_num, it is local to each and every use of the function. The function keeps calling itself until it returns the answer 1. It then works its way back through all the calls until it has completed the final multiplication, when it returns the answer. The limit of 35 on the input number prevents the answer being too big for the computer to handle.

Passing arrays to functions and procedures

BBC BASIC allows you to pass an entire array (rather than just a single element) to a function or procedure. Unlike single values or strings, which are usually passed to a function or procedure by value, an array is passed by reference. That is, a pointer to the array contents is passed rather than the contents themselves. The consequence of this is that if the array contents are modified within the function or procedure, they remain modified on exit from the function or procedure. In the following example the procedure PROC_multiply multiplies all the values in a numeric array by a specified amount:
DIM arr(20)
FOR n%=0 TO 20 : arr(n%) = n% : NEXT
PROC_multiply(arr(), 2)
FOR n%=0 TO 20: PRINTarr(n%) : NEXT 
END
:
DEF PROC_multiply(B(), M)
LOCAL n%
FOR n% = 0 TO DIM(B(),1)
  B(n%) = B(n%) * M
NEXT
ENDPROC
Note the use of DIM as a function to return the number of elements in the array.

The advantage of passing an array as a parameter, rather than accessing the 'global' array directly, is that the function or procedure doesn't need to know the name of the array. Ideally a FN/PROC shouldn't need to know the names of variables used in the main program from which it is called, and the main program shouldn't need to know the names of variables contained in a function or procedure it calls. This is a principle known as information hiding and is especially important in the case of functions and procedures contained in an INSTALLed library.

Passing parameters by reference

Although function and procedure parameters are usually passed by value, it is possible to specify that they should be passed by reference by using the keyword RETURN in the definition:
DEF PROCcomplexsquare(RETURN r, RETURN i)
If a parameter passed by reference is modified within the function or procedure, it remains modified on exit. This is the case even if the actual parameter and the formal parameter have different names.

Suppose you want to write a function which returns a complex number. Since a complex number is represented by two numeric values (the real part and the imaginary part) a conventional user-defined function, which can return only a single value, is not suitable. Conventionally you would have to resort to using global variables or an array or structure to return the result, but by passing parameters by reference this can be avoided.

The following example shows the use of a procedure to return the square of a complex number:

INPUT "Enter complex number (real, imaginary): " real, imag
PROCcomplexsquare(real, imag)
PRINT "The square of the number is ";real " + ";imag " i"
END
DEF PROCcomplexsquare(RETURN r, RETURN i)
LOCAL x,y 
x = r : y = i
r = x^2 - y^2
i = 2 * x * y
ENDPROC

You can use a similar technique to return strings rather than numbers. The following example, rather pointlessly, takes two strings and mixes them up by exchanging alternate characters between them:

INPUT "Enter two strings, separated by a comma: " A$, B$
PROCmixupstrings(A$, B$)
PRINT "The mixed-up strings are " A$ " and " B$
END
DEF PROCmixupstrings(RETURN a$, RETURN b$)
LOCAL c$, I%
FOR I% = 1 TO LEN(a$) STEP 2
  c$ = MID$(a$,I%,1)
  MID$(a$,I%,1) = MID$(b$,I%,1)
  MID$(b$,I%,1) = c$
NEXT I%
ENDPROC
You can also use this facility to create a variable whose name is determined at run-time, something which is otherwise impossible to achieve:
INPUT "Enter a variable name: " name$
INPUT "Enter a numeric value for the variable: " value$
dummy% = EVAL("FNassign("+name$+","+value$+")")
PRINT "The variable "name$" has the value ";EVAL(name$)
END
DEF FNassign(RETURN n, v) : n = v : = 0

Returning arrays from procedures and functions

It is possible to declare an array within a function or procedure and return that new array to the calling code. To do that you must pass an undeclared array as the parameter and use the RETURN keyword in the function or procedure definition:
PROCnewarray(alpha())
PROCnewarray(beta())
PROCnewarray(gamma())

DEF PROCnewarray(RETURN a())
DIM a(3, 4, 5)
...
ENDPROC
If you pass an array which already exists, it must have an identical format to the declaration within the function or procedure.

Note: You cannot use this technique to declare LOCAL arrays.

Indirect procedure and function calls

You can call a procedure or function indirectly using a pointer, rather than by specifying the actual name of the procedure or function:
pptr%% = ^PROC1
REM ....
PROC(pptr%%)
The above code calls the procedure PROC1 (without any parameters) but the name of the procedure is specified separately from the call itself. This allows you to do things such as determine which procedure or function should be called at run time in a more flexible way than can be achieved using ON PROC or EVAL. For example you could have an array of function pointers and select the wanted one according to an index value:
DIM fptr%%(4)
fptr%%() = ^FNzero, ^FNone, ^FNtwo, ^FNthree, ^FNfour
REM ....
answer = FN(fptr%%(index%))
If the procedure or function takes parameters then you must include a pair of parentheses when you use ^ to find the pointer value:
DIM fptr%%(4)
fptr%%() = ^FN0(), ^FN1(), ^FN2(), ^FN3(), ^FN4()
REM ....
answer = FN(fptr%%(index%))(parameters)
There are two very important caveats when using indirect procedure and function calls. Firstly, at least one conventional PROC or FN call must have been made before you attempt to read a procedure or function pointer (if not a No such FN/PROC error will result). Secondly, you must be very careful to make an indirect call only with a valid procedure or function pointer; using an incorrect value may well crash BASIC.

Passing procedures and functions as parameters

You can use a procedure or function name as a parameter to a procedure or function. For example you might want to write a library function to create a push button; you could pass to the function the name of a procedure which will be called when the button is pressed:
hb% = FN_button("Name",10,100,32,12,PROCbut)
Within FN_button an integer variable should be used to receive the procedure or function's address. An indirect procedure or function call can then be made using this variable:
DEF FN_button(text$,x%,y%,cx%,cy%,RETURN pr%)
REM ....
IF button_pressed PROC(^pr%)
Note that the use of RETURN is mandatory. In the case of a function being passed rather than a procedure, this ensures that it's the function's address not its value that is passed into FN_button. As before you must be very careful to use only a valid procedure or function pointer; using an incorrect value (e.g. as a result of omitting the ^) is likely to crash BASIC.


Input editing

When a BBC BASIC for Windows or BBC BASIC for SDL 2.0 program is waiting for user input (as the result of an INPUT statement), or when it is waiting for a command in immediate mode, the following editing keys are active:

KeyOperation
Insert Toggle between insert and overtype modes. In overtype mode any character typed will over-write the existing character at that position on the line. In insert mode any character typed will 'push' the rest of the line (if any) to the right to make room. The mode is indicated by the shape of the cursor: normally an underline indicates insert mode and a solid block indicates overtype mode.
Backspace Delete the character immediately to the left of the cursor, and move the cursor left one position. The rest of the line (if any) is moved left to fill the gap. If the cursor is already at the beginning of a line, the key has no effect.
Delete Delete the character at the cursor position and move the rest of the line (if any) left to fill the gap. If the cursor is already at the end of the line the key has no effect.
Home Move the cursor to the start of the line.
End Move the cursor to the end of the line.
Left Move the cursor one character to the left; if the cursor is already at the beginning of a line, the key has no effect.
Right Move the cursor one character to the right; if the cursor is already at the end of the line, the key has no effect.
Ctrl+U Delete everything to the left of the cursor. If the cursor is already at the beginning of the line the key has no effect.
Ctrl+V Paste in the contents of the clipboard (if any). If there is more than one line of text in the clipboard only the first line is pasted; the rest is discarded.
Tab Enter Copy key editing mode.
Shift/Tab Exit Copy key editing mode.

In situations when use of the INPUT statement is unacceptable (for example if a proportional-spaced font has been selected, or it's important that event interrupts not be delayed) you can use a replacement written in BASIC. Examples are the FNpsinput routine listed here or the FNinput routine contained in the NOWAIT library.

Copy key editing

When a BBC BASIC for Windows or BBC BASIC for SDL 2.0 program is waiting for user input (as the result of an INPUT statement), or when it is waiting for a command in immediate mode, Copy Key editing is available. As the PC keyboard does not have a dedicated COPY key the Tab key is used for this purpose. Unlike the BBC Micro, copy-key editing must be initiated by first pressing this key.

When you press the Tab (copy) key, two cursors are displayed. The large 'block' cursor shows where anything you enter will appear; it is called the write cursor. The other small flashing cursor is the one that can be moved around by the cursor control keys. For reasons which will become clear later, this is called the read cursor.

You can exit the copy-key editing mode (without pressing Enter) by pressing Shift/Tab; this restores the normal line-editing features. Thus, you can switch between the two forms of editing by using Tab and Shift/Tab.

The copy key

Once you have moved the read cursor to the line on the screen you wish to edit, you may copy the characters on that line to the write cursor's position by pressing the Tab (copy) key. Every time you press the Tab key, the character at the read cursor's position will be written to the write cursor's position; then, the read cursor and the write cursor will move on one character position. At any time you wish, you may type characters on the keyboard and they will be entered at the write cursor's position. The write cursor will move on one character position after each character is typed on the keyboard.

You don't have to confine yourself to copying from a single line. You can move the read cursor around the screen and copy any character or sequence of characters displayed in any position on the screen. You can copy the same thing over and over again, or skip bits of a line by moving the read cursor over words you don't want in the new line.

The backspace key

The backspace key works as it normally does. When you press the backspace key, the character to the left of the write cursor is deleted and the write cursor moves back one character position. Thus, you can delete characters you have copied or entered in error.


Hardcopy output to a printer

(BBC BASIC for Windows only, currently BBC BASIC for SDL 2.0 does not support hardcopy output).
BBC BASIC is a little unusual in the way it controls hardcopy output to a printer. Many BASIC dialects use the LPRINT keyword, which acts like PRINT but outputs to the printer rather than to the screen. BBC BASIC does not have LPRINT, but instead enables or disables printer output using VDU commands.

Printer output is enabled using VDU 2 and disabled using VDU 3. Therefore to output to both the printer and the screen you would do:

VDU 2 : REM Turn printer on
PRINT "Here is some output to the printer."
PRINT "It will also appear on the screen."
VDU 1,12,3 : REM Eject page and turn printer off
Note the VDU 1,12; that tells the printer that the page is complete and instructs it to print and eject the sheet. Without it the page won't be printed until your program exits or a timeout occurs.

If you want to output only to the printer and not to the screen you can redirect output to the printer using *OUTPUT 15:

*OUTPUT 15
PRINT "Here is some output to the printer."
PRINT "It will not appear on the screen."
*OUTPUT 0
VDU 2,1,12,3
If your printout isn't the right size, or you want to select a different font, add a *PRINTERFONT command at the beginning of your program, for example:
*PRINTERFONT Courier New,12
*MARGINS 10,10,10,10
The *MARGINS command resets the margins to the default size of 10 mm.


Labelling program lines

BBC BASIC was one of the first dialects of BASIC to support structured programming and hence to make the use of GOTO unnecessary. Nevertheless GOTO (and GOSUB) are still provided, principally to make it easier to convert programs from older dialects. In a similar way BBC BASIC for Windows and BBC BASIC for SDL 2.0 provide the ability to label program lines instead of using line numbers. A label may be used as the target of a GOTO, GOSUB or RESTORE:
A = 5
(loop)
PRINT A
A = A + 1
IF A < 10 THEN GOTO (loop)
Note that the label must be enclosed in parentheses, and be the first thing on a program line. Labels and variables share the same namespace so you should not normally use the same name for a label and a variable in your program.

Labels are limited in much the same way as line numbers: they are global to the whole program and cannot be used in INSTALLed or CALLed modules. You are advised only to use labels for debugging, or when converting a program from another dialect of BASIC.


Multilingual (Unicode) text output

By default all text output to the screen or the printer uses the 8-bit ANSI character set; the only foreign language characters available are accented letters. If you need to output text in other alphabets (such as Arabic, Greek, Hebrew or Cyrillic) - without needing to select a different font for each one - you can do so by enabling UTF-8 mode, which you do using a variant of the VDU 23,22 command in conjunction with selecting a suitable Unicode font (e.g. Arial, Courier New or Times New Roman, in BBC BASIC for Windows or DejaVuSans in BBC BASIC for SDL 2.0).

Once UTF-8 mode has been selected, all text output to the screen or the printer uses the multibyte UTF-8 character set; each character requiring 1, 2 or 3 bytes depending on its position within the Basic Multilingual Plane. The following code snippet illustrates its use:

      VDU 23,22,640;512;8,16,16,8
      *FONT Arial,24
      aleph$ = CHR$(&D7)+CHR$(&90)
      thorn$ = CHR$(&C3)+CHR$(&9E)
      euro$ = CHR$(&E2)+CHR$(&82)+CHR$(&AC)
      PRINT aleph$ " " thorn$ " " euro$
In BBC BASIC for SDL 2.0 substitute this font selection:
      OSCLI "FONT """+@lib$+"DejaVuSans"",24"
Because UTF-8 text uses a variable number of bytes for each character, features that rely on counting characters (for example COUNT, WIDTH and TAB(X)) won't work reliably, even when a fixed-pitch font is selected.

Left CONTENTS

CONTINUE Right


Best viewed with Any Browser Valid HTML 3.2!
© Richard Russell 2024