Skip to content

Instantly share code, notes, and snippets.

@eparadis
Last active May 15, 2021 05:11
Show Gist options
  • Save eparadis/2e86e94dd8b979155a6ba4f0ef5b38e4 to your computer and use it in GitHub Desktop.
Save eparadis/2e86e94dd8b979155a6ba4f0ef5b38e4 to your computer and use it in GitHub Desktop.
A modified BASIC designed to support language extensions

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
@eparadis
Copy link
Author

edits:

  • clarified what FCN() does
  • introduced commas to separate arguments to the GOSUB and RETURN variants

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment