R.T.Russell Home Page

Chapter 16 - User Defined Routines: PROC




We have already seen there are built-in commands and functions that are the backbone of BASIC. Such examples are CLS, which clears the screen and LOG(X) which returns the log of X. Sooner or later, as programs get bigger, you'll find that you are using the same lines of code several times in a program. This section shows how we can make our own commands out of these common lines and then call them up at will.

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:

CLS
PRINT TAB(2);"BBC BASIC Tutorial"
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:
REM PROC demo
PROC_ScreenSetup
INPUT A$
PROC_ScreenSetup
END
      
DEF PROC_ScreenSetup
CLS
PRINT TAB(2);"BBC BASIC Tutorial"
ENDPROC
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.

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

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

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.

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
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 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
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:
DEF PROC_DoSomething(RETURN AString$)
This is termed 'call by reference'.

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.

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
Structures have no corresponding function, you just have to know the members associated with that structure.
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
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{})
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
LOCAL Variables

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:

REM LOCAL demo 1
PROC_DrawLine("*")
END
 
DEF PROC_DrawLine(Char$)
LOCAL Count%
FOR Count% = 0 TO 79
  PRINT Char$;
NEXT Count%
ENDPROC
Local variables follow conventions of normal (global) variables as regards naming and defining types.

You may also declare arrays and structures as LOCALs but to do this requires the use of an extra DIM statement:

REM LOCAL array
PROC_A
END
 
DEF PROC_A
LOCAL MyArray%()
DIM MyArray%(10)
REM Do something ...
ENDPROC
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 structure
PROC_A
END
 
DEF PROC_A
LOCAL MyStruct{}
DIM MyStruct{a%,b,c$}
REM Do something ...
ENDPROC
PRIVATE Variables

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.

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

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.

Left CONTENTS

CHAPTER 17 Right


Best viewed with Any Browser Valid HTML 3.2!
© Peter Nairn 2006