BBC BASIC provides two methods of doing this: PROC and FN. Both allow you to call a routine in the same way you use in-built BASIC commands. The difference is that PROC is used as a command (no return value) whereas FN behaves like a function, it returns a value that you can assign or use in an expression. Largely, with these differences aside, the structure and rules are the same so we'll deal with PROC first, but bear in mind that much of what is said will apply to FN as well.
Suppose you want to clear the screen and print a title at the top of the page. That's easy, you say, two lines of code:
Now, if we wish to do this several times, we have the choice of copying the lines each time or we can enclose them in a block of code that can be called up whenever we want. This is how we do it:
CLS PRINT TAB(2);"BBC BASIC Tutorial"
PROC stands for procedure, by the way. Before a PROC can be called, it must be defined. The start of the definition is denoted by the keyword DEF (for DEFine). The end is denoted by ENDPROC (no space) and all the code between is referred to as the body of the procedure. When the program runs, it gets to line 2 and makes a note of where it was. It then jumps off and runs the body of the PROC. When it finds ENDPROC, it goes back to where it left off and continues the program as before. A similar thing happens when it reaches line 4.
REM PROC demo PROC_ScreenSetup INPUT A$ PROC_ScreenSetup END DEF PROC_ScreenSetup CLS PRINT TAB(2);"BBC BASIC Tutorial" ENDPROC
It is common practice to place PROC definitions after the END of the main program, so the main body of the code is not interspersed with declarations. The name of the PROC follows the conventions for naming variables: start with a letter or an underscore (you can actually use a number as well, unlike variables) then include any alphanumeric characters. The first letter of the name must follow the word PROC, no spaces. It is common practice, though not compulsory to start with an underscore, just to make things a little easier to read.
So now we can print our title on the screen, what happens if we have different titles depending on the screen we wish to display? Well, we could write a different PROC for each screen, but if we have 10 screens, that's a lot of duplicate code. BBC BASIC allows us to pass a value into a PROC for that PROC to use in any way it sees fit, so we can pass a different title as a text string each time we call the PROC.
How useful is that? The text for the title is copied into the string variable Title$ and this is used throughout the body of the routine, just as it would a normal variable. Note that the contents of Title$ are only meaningful whilst the PROC is being processed.
REM Passing a value to a PROC PROC_ScreenSetup("First screen") INPUT A$ PROC_ScreenSetup("Second screen") END DEF PROC_ScreenSetup(Title$) CLS PRINT TAB(2);Title$ ENDPROC
A PROC can have as many values passed to it as you wish, the rule is that they must be of the same type and in the same order when called as in the line in which they are declared. For example, here is a variation that allows us to specify the title and where to print it on the top line.
When a variable is passed to a PROC, the procedure is free to manipulate the local value in any way it wants. As it is working with a copy, the original variable is not changed. This method of passing data is termed 'call by value'.
REM Passing a value to a PROC PROC_ScreenSetup(5, "First screen") INPUT A$ PROC_ScreenSetup(10, "Second screen") END DEF PROC_ScreenSetup(Col%,Title$) CLS PRINT TAB(Col%);Title$ ENDPROC
There are occasions when we want the routine to do something permanent to the contents of a variable. To do this we use the keyword RETURN in the declaration of the PROC. Change the declaration line and run again to see the effect:
REM Call by value demo MyString$ = "Hello, world" PRINT "Value before PROC ";MyString$ PROC_DoSomething(MyString$) PRINT "Value after PROC ";MyString$ END DEF PROC_DoSomething(AString$) PRINT "Value passed to PROC ";AString$ AString$="Goodbye, cruel world" PRINT "Value after changing ";AString$ ENDPROC
This is termed 'call by reference'.
DEF PROC_DoSomething(RETURN AString$)
You can pass entire arrays and structures into a PROC, not just single value variables. To do this, just use the name followed by empty brackets to indicate that it is not a plain old variable that we are dealing with. If you remember the use of DIM to find the size of an array in the section on arrays, you can now see an immediate use for it. If an array is passed, we can use it to check that it has the correct number of dimensions of the right size. This helps eliminate out of bounds errors.
Structures have no corresponding function, you just have to know the members associated with that structure.
REM Passing an array DIM IntArray%(2,3) PROC_ArraySize(IntArray%()) END DEF PROC_ArraySize(Array%()) PRINT "This array has "; PRINT DIM(Array%());" dimensions." ENDPROC
Arrays and structures are always passed by reference. The reason is that an array or structure can be (and frequently is) big, that's why we use them. To make a complete copy would be expensive, both in terms of memory and the processing time it takes to physically copy each value. This means that if you change a value in a passed array or structure, the change is permanent.
REM Passing a structure DIM Struct{A%,B$} Struct.A%=23 Struct.B$="Twenty-three" PROC_PrintStruct(Struct{}) END DEF PROC_PrintStruct(St{}) PRINT St.A% PRINT St.B$ ENDPROC
LOCAL Variables
REM Passing a structure DIM Struct{A%,B$} Struct.A%=23 Struct.B$="Twenty-three" PROC_PrintStruct(Struct{}) PROC_IncStruct(Struct{}) PROC_PrintStruct(Struct{}) END DEF PROC_PrintStruct(St{}) PRINT St.A% PRINT St.B$ ENDPROC DEF PROC_IncStruct(St{}) St.A%=24 St.B$="Twenty-four" ENDPROC
The idea that we can pass values into a PROC is an important one, it means that we can write routines which are portable between programs, so once you've tested a routine, you can paste it into your next program, put it in a library or give it to all your BBC BASIC friends without redeveloping it. In order to do this most effectively, we need to ensure that every variable used in the PROC is only used in the PROC and nowhere else. Values passed from outside the procedure have already been dealt with. What we need is a method of making a variable belong solely to a routine. It's about now that we run into the idea of local variables.
A local variable is a variable that exists whilst the body of the procedure is being executed and is only accessible to that procedure. There is a keyword necessary to let BASIC know that the variable is to be treated as local and it is called LOCAL. A variable defined as local comes into existence when the routine is called, can be used anywhere in the body of the code and is discarded when the routine exits. Let's see an example, suppose we want to draw a line of characters on our PROC:
Local variables follow conventions of normal (global) variables as regards naming and defining types.
REM LOCAL demo 1 PROC_DrawLine("*") END DEF PROC_DrawLine(Char$) LOCAL Count% FOR Count% = 0 TO 79 PRINT Char$; NEXT Count% ENDPROC
You may also declare arrays and structures as LOCALs but to do this requires the use of an extra DIM statement:
The LOCAL statement here tells BASIC it will need to reserve some space for an array, the next line with the DIM tells it how much. LOCAL structures are achieved in the same way:
REM LOCAL array PROC_A END DEF PROC_A LOCAL MyArray%() DIM MyArray%(10) REM Do something ... ENDPROC
PRIVATE Variables
REM LOCAL structure PROC_A END DEF PROC_A LOCAL MyStruct{} DIM MyStruct{a%,b,c$} REM Do something ... ENDPROC
Once you are familiar with LOCAL variables, PRIVATE ones are easy. A PRIVATE variable is declared in the same way as a LOCAL variable, but uses the keyword PRIVATE to define it. It is valid during the execution of the routine but not outside it, just like a LOCAL. The difference is a PRIVATE variable is not forgotten about when a procedure is finished, its value is remembered and re-used the next time the routine is called.
First time round, MyInt% is created and set to zero, the value is printed out for all to see. MyInt% is then increased by 100. The second time PROC_A is called, MyInt% has a value from the previous call, this is shown by the PROC printing the value again.
REM PRIVATE variable PRINT "Calling first time" PROC_A PRINT "Calling second time" PROC_A END DEF PROC_A PRIVATE MyInt% PRINT "Value of MyInt% = ";MyInt% MyInt%=MyInt% + 100 ENDPROC
Scope
Up to encountering the use of local and private variables, all variables have been accessible anywhere in the program, they are known as 'global' variables. Global variables can be accessed anywhere in the program, even after we have finished chopping it up into manageable sections with PROC and FN. If you declare a variable in a routine without first telling BASIC it will be local or private, this variable will be global just like all the rest. Programmers (of which you are one) refer to this 'visibility of a variable' as scope, probably because it's easier to say. When a variable is not visible, it is said to be 'out of scope'.
Exercises
1) Modify the PROC_ScreenSetup to accept the background and foreground
colour.
2) Write and test PROC_Greater(A%,B%) which compares A% and B%. If the
A% > B%, do nothing, if B% > A%, exchange the two values
using a
local variable. You'll need to pass by reference so the calling program
can print the results.
CONTENTS |
CHAPTER 17 |