Chapter 8 : More on Variables and Errors
We've seen how to add, subtract and multiply variables but there are lots more things you can do with them - the most obvious is division, which you do with a slash (/) symbol.
In mathematics, some operations are done before others. You do multiplication and division after working out what is inside brackets, but before addition or subtraction.
In Basic, the operations are divided into groups, which are carried out in order:
The AND, OR and exclusive OR operations can be applied to variables or to the bits of a number. This is explained in Section 9, along with left and right shifts.
For example, if you want to do a whole number division of x% by y% + 3 and make the result the value of z%, don't put:
z%=x% DIV y%+3
This will do a whole number division of x% by y%, then add 3 to it. Not quite what we want. Use brackets instead, so that the line reads:
y%=x% DIV (y%+3)
Arrays and DataBack in Section 5, we met the program for working out the day of the week of any date, which had a rather cumbersome way of setting the variables monnum% and days$. We can improve on this substantially by using array variables.
An array is rather like a set of variables, all with the same name but identified by a number which follows the name. To see how we use them, together with an easy way of getting numbers or strings into them, let's rewrite the Days program as Days2:
Straightaway we've shortened the program from 38 lines to 24.
If we want to use an array, we must tell Basic about it and how many elements it has so that it will allocate an appropriate amount of memory to hold it. This is the job of the DIM keyword, short for dimension, in line 40.
We're defining two variables, separated by a comma. The number in brackets after each array name is the highest element number we shall use with this array. In fact, the number of elements is 1 more than this, as the elements are numbered from zero. To keep things simple, we won't use monnum%(0) but just use monnum%(1) to monnum%(12), since these numbers correspond to the month number which we type in.
The end result of the calculations is a number between 0 and 6 which represents the day of the week we're looking for, 0 for Sunday and 6 for Saturday. In order to print the name of the day, we will print one of the elements of day$(), whichever one holds the appropriate name.
How, though, do we get all the names into the elements of the array? We could use a number of lines of Basic, such as day$(0)="Sunday":day$(1)="Monday" etc., but that would be just as long-winded as our original method. This is where the READ command comes in.
Learning to READWhen Basic encounters a READ command, it looks for a DATA keyword. It finds the first of these in line 230, followed by a series of numbers, separated by commas. It sets the value of the variable following the READ command to the first of these numbers and also remembers whereabouts in the program the number was. Each time it encounters a READ command, it takes the next DATA number after the last one it used. If it gets to the end of the line, it looks for another DATA keyword on a later line.
These lines, called DATA statements, can be placed anywhere you like in the program. You may like to put them immediately after the part containing the READ command (but outside any loops), to make it clear what they're associated with. In this case, we've put them at the end, so as not to interrupt the flow of the program, which would make it more difficult to understand.
You can use the READ command with either numeric or string variables, but the data must match the type of variable you're READing, otherwise you will get an error message.
All our READing is done in FOR ... NEXT loops. This makes it easy for us to go through all the elements of monnum% and day$, putting a value into each of them. The first time we go round the loop in line 50, m% is 1 so we read a zero into monnum%(1), which is the first number after the DATA keyword in line 240. The next time round, m% is 2 and we read a 3 into monnum%(2) and so on until we get to monnum%(12), which we make 5. At this point, Basic knows that we've used up all the numbers in line 230, so the next READ command, which occurs in line 60, takes data from line 240.
Now we have values in all the elements of monnum%() and day names in all those of day$() and it's a simple matter to use the appropriate element of monnum%() in line 170 and to print the appropriate day of the week in line 190.
You can reset the point where Basic takes its data from using the command:
where xxx is the line number. To avoid referring to a particular line number, you can type:
This will make the program use the first DATA statement following this line.
More DimensionsThe arrays which we've used so far are one-dimensional arrays. You can have an array with two or more dimensions by including the appropriate number of figures in the brackets, for example:
This will set up a two-dimensional array called matrix%(), having a total of (4+1) times (5+1), that is 30 elements. You can see that the size of multi-dimensional arrays can easily grow very large and gobble up vast amounts of memory if you DIM them larger than you really need them!
By the way, once you've defined the size of your array with the DIM keyword, you can't alter it. Basic has allocated the appropriate amount of memory to your array and it can't be changed.
Letters and ASCII CodesYour computer is very good at handling numbers. It stores them in its memory and its ARM or StrongARM processor deals with them very quickly.
How, though, does it handle text? The answer is that it uses a code number to represent each letter or other character that can appear on the screen.
Text in computers has been around for almost as long as computers themselves, as a result of which the code system for dealing with it has been thoroughly standardised. Because of this, it's possible to write a text file on your RISC OS machine, using a text editor, save it on a PC format disc and use it with a word processor on a PC or any other machine that handles text files. This code is called the ASCII code, which stands for American Standard Code for Information Interchange.
The code consists of numbers up to 255, though 128 and above are not used as often as 127 and below and may vary between different versions of the code. The numbers which mainly interest us are the ones from 32 to 127, which represent characters shown in the table on the following page.
Code 32 is a space and 127 means backspace and delete. Codes below 32 are used for other purposes and don't print characters on the screen. We'll meet them later in Appendix 2.
GET and INKEYYou can see ASCII codes in action by using the GET keyword. This is a kind of Basic variable which takes its value from a key which you press. Each time Basic encounters GET in your program it checks to see if you've pressed a key, and waits for you to do so if you haven't. There are two versions of GET; GET$ produces a one-character string containing whichever character you typed, and GET produces a number, which is the ASCII code of the character.
Start up Basic's command level and enter:
REPEAT PRINT GET:UNTIL FALSE
The Basic prompt will disappear and the cursor (unless you're using a task window) will blink at you, waiting for you to press a key. Try typing a capital 'A'. The number 65 will appear on the screen. If you check the list, you will see that 65 is the ASCII code for A.
You can try this with any characters you like - capital or lower case - and press Esc when you get tired of it, but before you do so, try typing a few letters with Ctrl held down. You'll find that you get the ASCII code for the capital letter with 64 subtracted from it. Ctrl+A for example produces an ASCII code of 1.
When you use GET, your program will always wait for you to press a key and will not continue until you do. This may not be satisfactory for certain types of operation - you might, for example, have objects moving around the screen and you won't want them to freeze until you press a key. In this case, you need the INKEY keyword.
and see what happens.
Just as with GET, the Basic prompt disappears and you're left staring at the cursor. After five seconds, however, the number -1 will appear, followed by the prompt. If you press a key within this time, the prompt will reappear immediately and the number will be the ASCII code of the key.
Unlike GET, INKEY does not wait forever for you to press a key but puts a time limit on your actions, determined by the number in brackets following the keyword. Because this number is in centi-seconds, INKEY(500) waits a maximum of five seconds. If you press a key during this time, the waiting period ends immediately, you get the code for the key pressed and the program continues. If you don't press a key, INKEY returns -1.
Like GET, there is a variation of INKEY called INKEY$. This works in the same way as INKEY except that it returns a one-character string containing the character typed. If you don't press a key within the time limit, it returns a null string (zero characters long).
You can check for a keypress without waiting at all by using INKEY(0). It is most unlikely, of course, that you will press a key at the exact instant when this command is executed but it is not that critical. The machine stores keypresses in the keyboard buffer until a program reads them so x%=INKEY(0) simply checks to see if a key has been pressed since the last time the buffer was read.
You can prevent spurious keypresses interfering with your program by flushing the buffers, that is emptying them. This can be done with a star command:
VDU CodesNow for the other end of things - sending ASCII codes to the screen. We do this with the VDU command. After escaping from your previous loop, type:
A capital A will appear on the screen with the Basic prompt to the right of it. We've sent 65 to the screen, which caused it to print the A, but we didn't tell it to go to a new line, so the prompt appeared on the same line.
... with the Basic prompt to the right of it ...
You can follow a VDU keyword with a string of numbers, or variables, separated by commas, for example:
will print ABC.
You can try sending codes less than 32, but some of them will produce unpredictable results and you may end up having to reset your machine. Try:
This will make the machine beep, and typing Ctrl G will produce the same result, because it produced ASCII code 7. You can liven up quite a few programs with VDU 7!
VDU 14 (Ctrl N) and VDU 15 (Ctrl O) will put the screen into and out of page mode, as we saw in Section 5 and VDU 12 (Ctrl L) will clear the screen.
If you enter VDU 13 (Ctrl M), the cursor will return to the beginning of the line and the Basic prompt will appear there. If you enter it on its own, it will look as though nothing has happened. You may have discovered when you were doing your GET experiments that 13 is the ASCII code produced by the Return key, which would normally cause the cursor to move down one line, so why does it behave differently here?
The explanation is that, in normal use, a Return character is followed by a Line Feed, which is ASCII code 10 (Ctrl J). This causes the cursor to move down one line. When you PRINT something, or type in a line of Basic, pressing Return causes both these codes to be sent to the screen, producing the desired effect.
Why, you may be wondering, do the ASCII codes for figures start at 48, the codes for capital letters at 65 and for lower case letters at 97? Why do ASCII codes go up to 255, the same figure that kept cropping up when we were dealing with colours? All will be revealed in the next section!
Further Use of Error HandlingOur Days2 program earlier in this section included an instruction in line 130 to report that we had made an error. It also interrupted what we were doing, in this case by sending us back to the beginning of the REPEAT ... UNTIL loop. These two operations are usually performed by the machine's error handling system, so let's examine how we can make it do the job for us.
Here is a new version of the program Days3, with some alterations:
The first thing we've done is to add a second error handler in line 70. You can have as many error handlers as you like in a program; defining a new one usually makes Basic forget about the previous one. If you want to alter the way in which your program handles errors just temporarily, you can precede the new error handler with:
This makes Basic remember about the previous error handler. When you've finished with the new one, the command:
will reactivate the earlier one.
In our program, we keep the original error handler in line 30, in case anything goes wrong in lines 40 to 60.
Using Two Error HandlersThe positioning of the new handler in line 70 is very important. Because error handling from this point onwards will be more complex, we'll use a procedure to deal with it, called by the new handler. Under certain circumstances, as we shall see shortly, the program will not stop if there is an error but will continue, picking up its operation following the error handler. If we had simply modified the existing handler in line 30, this would have resulted in lines 40 to 60 being executed again. Line 40 would have given us an error message because we can't DIM an array more than once and lines 50 and 60 would have told us that we had run out of data.
If an error occurs in the main REPEAT ... UNTIL loop, the program jumps to the error handler in line 70. Because this is defined using the ON ERROR keywords, Basic forgets about any procedures, functions, FOR ... NEXT or REPEAT ... UNTIL loops or any other structures which it may have been executing. It just assumes that it is executing the main part of the program and not in any procedures, functions or loops. Because the error handler is located immediately before the main REPEAT ... UNTIL loop, any error within the loop which doesn't actually stop the program will cause it to go back to the beginning of the loop and start again (in the previous version this was done by the UNTIL FALSE instruction following the error message).
You can create an error handler which remembers all the procedures, loops etc. being executed by putting the word LOCAL after ON ERROR. You can find further details of this in Appendix 1.
We've put PROCerror at the end of the program. Because the previous version didn't have any procedures, it had no need of an END statement, so we've added one. It could have been put after the DATA statements, but it seems appropriate to stop execution of the program after line 250, though it makes no practical difference.
Error Lines and Error NumbersAnything within the machine which generates an error has to produce two things; an error number and an error message. Errors in Basic programs also produce the line number on which the error was detected. Basic makes the error number the value of variable ERR and, as we've already seen, the error line number the value of ERL.
We can generate our own error using the ERROR keyword followed by an error number and a message. Because our error handler is going to identify the nature of the error by its number, we must be careful not to use an error number which may crop up for other reasons, so we shall stick to numbers which won't occur elsewhere.
In RISC OS, a range of numbers from 1,073,741,824 onwards can be used within programs. For reasons which we will discover in Section 9, this number can be written as 1<<30, the next number as (1<<30)+1 and so on. Line 140 generates an error using this number as the error number, followed by the error message.
Making Use of the Error NumberWhen this error is detected, the program forgets about the FOR ... NEXT loop and jumps straight to the error handler in line 70, which passes it on to PROCerror at the end of the program. The first action of this procedure is to examine the error number, using a CASE ... OF structure. It may seem pointless using this structure with only one WHEN keyword but this will allow you to expand the program by adding other forms of error detection. You could, for example, generate an error if the month number is greater than 12, giving it error number (1<<30)+1, another one if the date number is too high for the month, giving it (1<<30)+2 and so on. Adding more WHEN lines to the procedure allows each of these to be treated differently. The reason for the brackets can be seen if you look at the order of arithmetic operations at the beginning of this section - '<<' is a 'shift left' operation which is normally performed after plus and minus. If we didn't put brackets round these two expressions, they would be taken as meaning '1<<31' and '1<<32', which is not what we want.
If the error number is 1<<30, the procedure simply prints the error message (preceded and followed by a blank line) and allows the program to continue. For any other error number, it carries out the usual practice of printing the message, reporting the line number and terminating the program.
The only other change in this version is that we are using GET$ instead of INPUT when asking if the user wants another go. This means that you no longer have to press Return after typing y or Y - a single keypress is sufficient.