Petit Computer Wiki
Advertisement

NOTE: This page applies only to SmileBasic V1 and SmileBasic V2 which have no built-in local variable support. If you have SmileBasic V3, for goodness' sake, use its built-in local variable feature, not the hack below. Also, the following examples and code are for pedagogy; they are not optimized for speed, they are optimized for learning.

So, you have just learned the Fibonacci sequence! The first number is 1, the second number is 1, and every number after that is the sum of the two previous ones. Cool! Let's write some code to calculate, say, the 10th Fibonacci number. It should be, (let's see, 1, 1, 2, 3, 5, 8, 13, 21, 34,) 55.

CLEAR
FIB_IN=10
GOSUB @FIB
PRINT "Result=";FIB_OUT
END

'===========================
@FIB
' First, check if the 'in' value is one of the two base cases:
' if the 'in' value is 1 or 2, the 'out' value is 1.
IF (FIB_IN<3) THEN FIB_OUT=1:RETURN

' Find the number one step earlier in the Fibonacci sequence.
FIB_IN=FIB_IN-1
GOSUB @FIB

' Store the result in a temporary variable (FIB_OUT is about to get written over again).
TEMP=FIB_OUT

' Now, find the number two steps earlier in the Fibonacci sequence.
' I've already subtracted one from FIB_IN, so I'll just subtract one more.
FIB_IN=FIB_IN-1
GOSUB @FIB

' Add the result to the temporary variable.
TEMP=TEMP+FIB_OUT

' Now, TEMP contains the answer that the subroutine calculcated.
FIB_OUT=TEMP

RETURN

RUN and get... 9? That's not right.

The reasons for getting the wrong answer from the above code are numerous; but they all boil down to the fact that the variables are all global. For example, consider the value of TEMP, when you try to add FIB_OUT to it. The value stored in the variable will be the value assigned to the variable most recently, which is not necessarily the value assigned a few lines up: in those few lines, there has been another call to @FIB, which may have written another value to TEMP.

So, I decided to make a framework for local variables. And here is an example:

CLEAR
' The maximum recursion depth permitted is limited by the size of the array, STACK.
DIM STACK(1000)
' (Initializing STACKP (STACK Pointer) to 0 is technically not necessary after a CLEAR.)
STACKP=0

FIB_IN=10
GOSUB @FIB
PRINT "Result=";FIB_OUT
END

'===========================
@FIB

' First, check if the 'in' value is one of the two base cases:
' if the 'in' value is 1 or 2, the 'out' value is 1.
IF (FIB_IN<3) THEN FIB_OUT=1:RETURN

' Allocate two local variables.
STACKP=STACKP+2
' The two local variables are:
' STACK(STACKP-2) (which I will call L2)
' STACK(STACKP-1) (which I will call L1)

' Store the original parameter in L2.
STACK(STACKP-2)=FIB_IN

' Find the number one step earlier in the Fibonacci sequence (number: L2-1).
FIB_IN=STACK(STACKP-2)-1
GOSUB @FIB

' Store the result in a temporary variable (L1).
STACK(STACKP-1)=FIB_OUT

' Now, find the number two steps earlier in the Fibonacci sequence (number: L2-2).
FIB_IN=STACK(STACKP-2)-2
GOSUB @FIB

' Add the result to the temporary variable (L1).
STACK(STACKP-1)=STACK(STACKP-1)+FIB_OUT

' Now, L1 contains the answer that the subroutine calculcated.
FIB_OUT=STACK(STACKP-1)

' Deallocate the two local variables.
STACKP=STACKP-2

RETURN

This will give the correct result, 55.

The key here is that the 'local variables' are not any particular variable name, they are referenced by an index into STACK. That index is relative to STACKP. And, every time @FIB gets to the nontrivial code, STACKP is changed: so, STACK(STACKP-1), for example, is different when you're in a GOSUB-from-a-GOSUB-from-the-main-code than it is when you're in a GOSUB-from-the-main-code. When the code is being executed at a 'deeper' level, STACKP is a higher value than when it is being executed at a 'shallower' level.

Now, STACK needs to be an array large enough to hold all the run-time local numerical variables. This is roughly, the number of local numerical variables per subroutine times the maximum recursion depth (things get more difficult when you have two or more recursive subroutines, each with their own sets of local variables, but that's the basic the idea). If it is not large enough, you will get a Subscript out of range error sometime.

If you need local string variables, you'll need to declare an array STACK$ (which need not be the same size as STACK), and manage its own pointer seperately from STACKP.

Perhaps one of the most easily-overlooked aspects of having local variables in this way is deallocation. EVERY time local variables are allocated, the same number MUST be deallocated before the RETURN. And, since it's possible a single subroutine may have several places where it RETURNs, it's easy to forget to deallocate at one of those places. This will cause bugs, and a memory leak. Do not overlook the deallocation step.

And that's it. In terms of infrastructure and global variables, you need the STACK array (and/or the STACK$ array), and a pointer STACKP (or two). In terms of code for each subroutine that needs local variables, there is the allocation, e.g. STACKP=STACKP+2, the names of the local variables, e.g. STACK(STACKP-1) and STACK(STACKP-2), and the deallocation, e.g. STACKP=STACKP-2.

Advertisement