Extensible BASIC
A modified BASIC designed to support language extensions
6/12/2019
The common BASIC dialect on home computers of the 1970s and 1980s is Microsoft
BASIC 4.7 or very similar. Generally speaking, the dialect supports a single
user-defined FCN()
which allows for a user defined expression with a single
input parameter. Occasionally it will support machine-code methods with USR()
.
Neither of these are universaly available and USR()
is intrensically very
machine-dependent. What is the minimal modification to allow language-level
extensibility?
The most obvious feature is the definition of subroutines with their own
parameter stack. MS-BASIC 4.7 contains the GOSUB
keyword, which allows for
control flow call stacks. However, the variable space in each frame is still
global, and there is no method other than convention to allow for variable
scope.
For example, a subroutine using three local variables X
, Y
, and Z
could
expect each to be an array, with the current frame's set pointed to by and index
stored in W
. Each call to the subroutine (using GOSUB
) would need to
increment W
and set the elements of X
, Y
, and Z
respectively. Recursion
could be supported by another array V
storing a return value. Because MS-BASIC
does not support the stack data structure natively and arrays must be
pre-allocated, the maximum stack depth would need to be allocated in advance. It
would be difficult to regain this allocated memory space.
The management of stack frames inside a subroutine could be performed by a new
keyword such as FRAME()
.
30 X = FRAME(1)
40 Y = FRAME(2)
The parameter to FRAME()
would be an index into which variable in the current
frame should be retrieved. Values would be stored into the frame stack with
the call into the subroutine.
10 GOSUB 100, 1.23, 2.34, 3.45
...
100 X = FRAME(1)
110 Y = FRAME(2)
120 Z = FRAME(3)
130 REM X, Y, and Z now equal 1.23, 2.34, and 3.45, respectively
...
200 RETURN
Subroutines would return values by modification of the frame. These could then be retrieved with the same keyword.
10 GOSUB 100, 1.11, 2.22
20 A = FRAME(1)
30 REM A is now equal to 3.33
...
100 REM Add the first two items in the frame and return this sum
110 X = FRAME(1)
120 Y = FRAME(2)
130 Z = X + Y
140 RETURN Z
Recursion is supported.
10 GOSUB 100, 1
20 A = FRAME(1)
30 REM A now equals 5
...
100 REM recursively count to five
110 X = FRAME(1)
120 IF X = 5 THEN RETURN X
130 GOSUB 100, ( X + 1 )
Multiple return values are supported by supplying more than one value to
RETURN
.
10 GOSUB 100, 2.22, 3.33
20 A = FRAME(1)
30 B = FRAME(2)
40 REM A is now 5.55 and B is now -1.11
...
100 REM find the sum and difference of the first two items of the frame
110 X = FRAME(1)
120 Y = FRAME(2)
130 RETURN ( X + Y ), ( X - Y )
Multiple calls in sucession could create confusing FRAME()
calls.
10 GOSUB 100
20 A = FRAME(1)
30 REM A is now equal to 1.11
31 REM If FRAME(2) was accessed, it would be equal to 2.22
40 GOSUB 200
50 B = FRAME(2)
60 REM B is now equal to 8.88
61 REM If FRAME(1) was accessed, it would be equal to 7.77
...
100 REM return two values in the frame
110 RETURN 1.11, 2.22
...
200 REM return two different values in the frame
210 RETURN 7.77, 8.88
The value of a FRAME()
call could be unexpected if conditional flow results in
an additional or different GOSUB
call. A best practice would be to make all
calls to FRAME()
immediately after each call to GOSUB
.
A rather wordy but less invasive implementation would allow the assignment to
the FRAME()
. Access to the frame stack could get very confusing.
10 REM pass two values
20 FRAME(1) = 1.11
30 FRAME(2) = 2.22
40 GOSUB 100
50 A = FRAME(1)
60 B = FRAME(2)
70 REM A and B are 3.33 and -1.11 respectively
...
100 REM return the sum and difference of two values
110 X = FRAME(1)
120 Y = FRAME(2)
130 FRAME(1) = X + Y
140 FRAME(2) = X - Y
150 RETURN
The raises the concern of assigning a very large index of a FRAME
.
10 FRAME(1000) = 1.11
20 GOSUB 100
30 A = FRAME(500)
40 REM the value of A is now 2.22
...
100 REM double a value high in the frame
110 FRAME(500) = FRAME(1000) * 2
120 RETURN
This suggests an implementation of the frame where the parameter to FRAME()
is
simply a key in a dictionary. Could this key be a name instead?
10 FRAME("Q") = 1.11
20 GOSUB 100
30 A = FRAME("Z")
40 REM the value of A is now 2.22
...
100 REM double Q and return it as Z in the frame
110 X = FRAME("Q")
120 FRAME("Z") = X * 2
130 RETURN
This suggests some method of masking global variables with those of a FRAME, but it would quickly grow confusing. Both index abuse and the question of named frame values would be avoided by removing assignment to frame values directly.
Following the convention of BASIC, undefined frame values would simply be zero.
10 GOSUB 100
20 A = FRAME(1)
30 B = FRAME(2)
40 REM A and B are 1.11 and 0, respectively
...
100 REM set a single frame value
110 RETURN 1.11
edits:
FCN()
doesGOSUB
andRETURN
variants