Ascii-Art of the Tanasinn-Symbol

Microsoft QuickBasic and its little brother QBasic was the first programming language I was writing code for. It taught me programming. Today, I rather use Lisp, and many academic people tend to not really take this language serious. Well, it lacks of some features, and has special syntax for a lot of things that would not need it (LINE, etc.). But it really has some "modern" features one might not expect.

QuickBasic has a crude form of Continuations. Well, it is not like Scheme, where you can have many of them and call them as you like. And you have to use Tricks to make them work in a modern manner. And there are a lot of limitations that certainly could be avoided. But anyway, for a language like QuickBasic, this is still amazing.

Firstly, I think in times of GWBasic and more ancient Basic-Dialects, there were no Sub- and Function-Procedures (I am not quite sure about this, clarifications are welcome), but to create subprocedures, one used GOSUB and RETURN. Though QBasic has subprocedures, these instructions are still supported, and if one does not need recursion and is willing to relinquish the modularity of subprocedures, she gets some quite flexible tools (which are - admittedly - not far above Assembler, but as many Lispers will know, sometimes less is more).

GOSUB pushes the current Instruction Pointer on the stack, and jumps to a declared line number or label. It is like GOTO, just that you can jump back. After a GOSUB, a RETURN jumps back, or - if a line number or label is supplied - to this line number or label, but in any case, it pops one instruction pointer from the stack. So RETURN and GOSUB can be used to emulate subprocedures (without local variables of course) and with them some sort of continuation. Here is an example:

DIM i, j AS DOUBLE

in:
INPUT "Number:", i
j = 1
GOSUB fact
PRINT "Fact:", j
END

fact:
IF i < 0 THEN
  PRINT "Number must be greater than or equal 0!"
  RETURN in
ELSEIF i = 0 THEN
  RETURN
ELSE
  j = j * i
  i = i - 1
  GOSUB fact
  RETURN
END IF

There is also a CLEAR-Statement which purges the current Stack, but actually, I have not found a way to really use it so far. And of course, there is no direct possibility to just pop one element from the stack. One could use

RETURN popLabel
popLabel:

to achieve this. In the above program, one could therefore make the Stack not increase through the (tail-recursive) calls of fact.
However, it is more interesting to look at the error handling. QBasic has an instruction called ON ERROR GOSUB which a label can be passed. The program then jumps to that label, and with RESUME (not RETURN in this case - why ever) we can resume the program at some other label, and with RESUME NEXT, we can continue it.

DECLARE FUNCTION fact# (i AS DOUBLE)
DECLARE FUNCTION factinp# ()
ON ERROR GOTO handler
PRINT "Fact:", factinp
END

handler:
PRINT "Number must be greater than or equal 0!"
RESUME NEXT


DEFDBL F
FUNCTION fact (i AS DOUBLE)
IF i < 0 THEN
  ERROR 5
 ELSEIF i = 0 THEN
  fact = 1
  EXIT FUNCTION
 ELSE
  fact = i * fact(i - 1)
  EXIT FUNCTION
 END IF
END FUNCTION

FUNCTION factinp
begin:
  DIM i AS DOUBLE
  INPUT "Number:", i
  ON LOCAL ERROR GOTO fail
  factinp = fact(i)
  EXIT FUNCTION
fail:
  ERROR 5
  ' if resumed, we just ask again
  RESUME begin
END FUNCTION


Oh, and this ON-Statement is even more flexible. For example, there is a predefined Timer-Interrupt-Caller, with which one could (in theory) implement a context switch and therefore some sort of preemptive multithreading (of course, this is not in the scope of the language anymore). The below example shows this. It can be closed via F4 when you run it - here you also see the On-Key functionality, which is nice.

KEY(4) ON
ON KEY(4) GOSUB ende

GOSUB 1

1 ON TIMER(1) GOSUB 2
RETURN 3
3 TIMER ON
DO: PRINT "Thread 1": LOOP

2 ON TIMER(1) GOSUB 1
RETURN 4
4 TIMER ON
DO: PRINT "Thread 2": LOOP

ende:

QuickBasic lacks of tail call optimization. There are, of course, possibilities to reduce the stack size anyway, using the STATIC statement. But this can be done with many other programming languages too. However, what can be done is to catch stack overflows - which is rather complicated using for example C - one would probably use libsigsegv for it under Unix. The following code counts to 1000 (which is of course not very useful, but easier to understand). It realizes a trampoline call to the subprocedure "test". However, "test" calls itself as long as possible, and as soon as it is not possible anymore, it uses the handling of the stack overflow to save the current argument and return, such that the trampoline call can be performed.

DECLARE SUB test (i%)
DIM SHARED context AS INTEGER
KEY(4) ON
ON KEY(4) GOSUB ende
ON ERROR GOTO exitCont

context = 0

DO: test context: LOOP

exitCont:
PRINT context

ende:
END

SUB test (i%)
  ON LOCAL ERROR GOTO savecontext
  IF i% < 1000 THEN
   test i% + 1
  ELSE
   context = i%
   ERROR 5
  END IF
  EXIT SUB
savecontext:
  IF ERR = 5 THEN
    ERROR 5
  ELSE
    context = i%
    RESUME NEXT
  END IF
END SUB


Besides that, it unfortunately does not have function pointers. There is a function called CALL ABSOLUTE, which can call a memory adress directly. However, then you cannot use your QuickBASIC anymore without compiling it first.

Notice that the above code samples are for QuickBASIC 7.1. For QBasic, they have to be slightly adapted, but should work anyway.