Procedures Richard Taylor with a program to allow your Spectrum to simulate the BBC's Proc commands. My program gives the Spectrum all the advantages of proce- dures and local variables - a BBC Basic nicety. To make the five new commands offered by the program easy to use, you simply put them in inconspicuous REM statements, without having to bother with the hassle of machine code calls. As you would expect, the program is written in machine code, about 1.5K of it. [...] You can reload at any time using: CLEAR 63764: LOAD ""CODE As I said earlier, the new commands are put in Basic REM statements, with a limit of one command per REM. For a program to use the new commands, its first line must be RANDOMIZE USR 63765. Procedure commands For the moment I'll concentrate on the commands directly connected with procedures: DEFPROC, PROC and ENDPROC. A procedure is a block of Basic coded preceded by a DEFPROC and terminated with an ENDPROC. To save the impersonal approach of calling blocks of code using line numbers, procedures use the more flexible and friendlier system of calling blocks of code by a name. Usually the name of a procedure would be short and give some indication of the procedure's function. You can put spaces in, but the computer ignores them. You can also put the names in either upper or lower case, or even a mixture of the two, but it doesn't make any difference. The same is true of the commands themselves, although it is wise to put them in upper case to make the program more readable. Sometimes it's a good idea to high- light the start and end of procedures in inverse video or in different colours to make it less difficult to locate when debugging. The general format of a procedure would be as follows: 8000 REM DEFPROC thing 8010 ........ 8020 ........ Basic code 8030 ........ ............ 8200 REM ENDPROC That's all very well, but you need some way of actually calling the procedures, so along comes PROC, the "hi-tech" equivalent of GO SUB just as ENDPROC is the equivalent of RETURN. Again, the PROC can be put in a REM statement any- where in the program. It is followed by the name of the procedure you want to call, so "PROC thing" calls the pro- cedure at line 8000. The space in between PROC and the name is optional. you might wonder how the program knows that there is a procedure called "thing" if the computer has never previously executed line 8000. When the RANDOMIZE USR is used, the machine code looks through every line in the program. If any line has a DEFPROC in it, then it looks up the procedure's name and what line it's on and stores that information in a special area of memory. In fact, this special area of memory is at the start of the variables area. The machine code sets up a string variable called @$ and puts any data about procedures and other info in that string. Since you can't change a @$ variable from Basic you can't corrupt it - unless you use some vicious POKEs! However, you can use CLEAR and scrub out all the variables. The program won't do anything drastic like crash but just gives an error report. As with GO SUBs you can nest procedures, but with a limited depth of 255 levels. There is one more twist to the story of procedures: the concept of parameters. When you write a normal Basic subroutine, it usually has to rely on variables defined somewhere else in the program to perform its functions. Although passing numbers to a subroutine in this way is quite workable, it's certainly not the most elegant of methods. Fortunately, there is a neat way of passing values to a procedure that works in a similar sort of fashion to the DEF FN and FN commands of normal Sinclair Basic. Say you want a procedure called 'print' that puts an X at a certain line and column on the screen. You would write it like this: 8000 REM DEFPROC print(x,y) 8010 PRINT AT y,x;"X" 8020 REM ENDPROC The contents of the brackets at the end of the DEFPROC statement define what variables are used by the procedure. The associated PROC might look something like this: 20 REM PROC print(10,12) When the print procedure is called, the number 10 is placed in x, and 12 in y. The x and y at line 8000 are called the formal parameters, the variables that will hold the values given by the PROC. A procedure can have as many formal parameters as you like, including none at all - in which case there's no need for the brackets. The variables themselves can be of any type normally found in Sinclair Basic, except for array variables of any sort, but it's unlikely that you would want to use these for passing values anyway. Therefore a, ab, a$ and a long named vari- able are all valid, but a(1,z) and a$(3) are not. In the PROC statement you can either use numbers, strings - enclosed in the usual quotes - or variables - don't mix these up with the formal variables - but there must be the same amount of them as in the corresponding DEFPROC and they must be of the right types. If the first DEFPROC formal variable is a string then the first PROC expression must also be a string. You're not allowed to do any mathe- matics in a PROC statement, so -1, 0.1 and "a" are valid but 1*2, SIN(0.1) and SR$(1) are not. The following proce- dure draws a rectangle of a specified size and position. 8000 REM DEFPROC rectangle(x,y,a,b) 8010 PLOT x,y 8020 DRAW a,0: DRAW 0,b 8030 DRAW -a,0: DRAW 0,-b 8040 REM ENDPROC Four numeric parameters The procedure has four numeric parameters. The first two give the position of the rectangle's bottom left corner, the penultimate one gives the width and the last one the height. PROC rectangle(88,68,80,40) draws a rectangle of height 40 and length 80 slap bang in the middle of the screen. The last two commands offered by the program, LOCAL and RECALL, are concerned with local variables. The concept of local variables can be difficult and confusing for one to grasp but, basically, it allows you to have two variables with the same name but with different values in the com- puter simultaneously. A lot of programming errors are caused by using the same variable twice for conflicting purposes. These sort of bugs are often particularly difficult to track down. Such a pro- blem shouldn't really occur because there are 26 string variables to choose from, 26 loop control variables and an infinite number of numeric variables. However, some vari- ables tend to get used a lot more than others. For instance a,b,c,d for numerics, a$,b$,c$ for strings and i and n for loop controls. Programmers seem to have an unexplainable aversion to using k, w, and the like. What the LOCAL command does is make a second copy of certain variables and store them in its safe cubby hole at the start of the variables area. Then with another command the second copy can be miraculously recalled. The point of the whole exercise is that if you use a new variable in a procedure then you can localise it before you actually get down to using it so that when you unlocalise when you're finished you can be sure that it's got its original value back again. Another part of the program won't even know that you've been secretly tampering with some of its variables. The LOCAL command is followed by one or more variable names, separated by commas. These are the names of the variables that you want localised. The command that does all the unlocalising business in a procedure is our previ- ously introduced friend, ENDPROC. As well as returning control to the line after the calling PROC, ENDPROC also unlocalises all variables that were localised in that particular procedure. For instance: 8000 REM DEFPROC useless 8010 REM LOCAL a,a$ 8020 LET a=1 8030 LET a$="This procedure doesn't do anything" 8040 REM ENDPROC does nothing because the two variables a and a$ changed by the procedure are localised so that when the procedure is terminated they are changed back to their original values. Mind you, that's only true if a and a$ were defined when the procedure was called. If they weren't then the LOCAL command would have great difficulty in localising them - it wouldn't stop with an error though - and ENDPROC would have as much difficulty delocalising them; with the result that ENDPROC allows the variables to retain their values as defined in the procedure. There is one loophole to all this: you might want to lo- calise the formal parameters of the procedure. The problem is that you can't, because by the time the computer reaches the first line of your procedure the formal variables have already been changed. To save such hassles, before the values of formal variables are changed the computer auto- matically localises them. Therefore, don't try and return values in one of the formal parameters of a procedure - it will only end in disaster. The last command to be discussed is RECALL. RECALL is a lonely command; it doesn't have any arguments after it. RECALL is much like an ENDPROC, except it doesn't do the 'return from procedure' bit. What it does do is unlocalise all previously localised variables in a procedure. If you have a great desire to do so, for some obscure reason, you can localise variables outside of a procedure using a combination of LOCAL and RECALL commands. The program adds a number of new error reports to Sinclair Basic. They are produced in much the same way as normal errors, with the exception that they are not prece- ded by an alphanumeric code. The line where the computer stops because of an error is not always where the error actually is. For instance, if the computer stops with 'Syntax error' on a PROC line then the error might lie with the associated DEFPROC. Below is a full list of all the new reports and their possible reasons for being produced. 'Invalid procedure name' - You've tried to give a proce- dure name that doesn't consist of just letters. 'Data area cleared' - You've used a CLEAR statement and erased the program's safe cubby hole at the start of the variables area. 'DEFPROC not found' - You've used PROC with the name of a procedure that doesn't exist. 'Return stack full' - You can 'only' nest procedures up to a limit of 255 levels. This is the error you'll get if you exceed that limit. This will only normally happen if you manage to write a procedure that calls itself, either directly or indirectly. 'ENDPROC without DEFPROC' - The computer has come across an ENDPROC and it wasn't executing a procedure at the time. [Note: this also happens if you stray into a procedure using normal Basic commands, for example if you put all your procedures at the start of the program and forget to jump over them.] 'PROC parameter error' - There's either a different number of parameters between the associated PROC and DEFPROC or some of them aren't of the right type. 'RECALL without LOCAL' - You've tried to use RECALL outside of a procedure without previously using LOCAL. 'Syntax error' - This can be caused because of a variety of reasons, such as missing out a comma or bracket. The program can also produce normal Basic errors, most notably error C - "Nonsense in BASIC" and 2 - "Variable not found". Listing 2 gives a simple demonstration of procedures as applied to a real, if very simple, application. The appli- cation I'm talking about is a program that allows you to draw lines around the screen using the cursor keys.