IN THE PROCESS OF BEING REWRITTEN. VERY VERY VERY OUT OF DATE!

The vbSlacker OnError subsystem.

Part of the reason why BASIC isn't so cross-platform is not because there's
no desire for it in the cyber community, but because to implement a BASIC
system, you need a development team that codes like a god, but thinks like
a kindergartener.

The biggest stumbling block in making a BASIC system is the error handling.
It's kinda like C++ and Java's try/catch blocks, but considerably less
structured. Here's a brief comparison between BASIC and Java:

public void throwsError(PrintWriter writer)
{
    try
    {
        writer.println("Write this string to file.");
    } // try

    catch (IOException ioe)
    {
        System.out.println("IOException writing to file!");
    } // catch
} // throwsError


in BASIC:

SUB throwsError(fileNo AS INTEGER)
    ON ERROR GOTO IOEXCEPTION
    PRINT #fileNo, "Write this string to file."
    EXIT SUB

IOEXCEPTION:
    PRINT "IOException writing to file!"
END SUB


Implementation of this sort of error handler is complicated enough, since we
need to play with the stack, but when we consider the language's RESUME
command, we have something truly complicated.

Here's our solution, in a nutshell.

First a practical example is needed, then we'll break it down.

Here's an error handler in BASIC, and what vbSlacker (roughly) produces from it:

---

SUB throwsError
    ON ERROR GOTO fixme
    PRINT #15, "Text!"
    EXIT SUB

fixme:
    OPEN "text.txt" FOR OUTPUT AS #15
    RESUME 0
END SUB

throwsError

REM end of program...

---

#include "BasicLib.h"

!!! write this.


If vbSlacker optimizations are enabled, specifically "disable error handling",
then the code a little more efficient, since -fomit-frame-pointer can be
passed to GCC. !!! are you sure?


Here's how it works:


When an OnEvent is triggered, it sees if there is an OnEvent handler for that
event type registered. (So if an ON TIMER event is triggered, no one cares how
many ON ERROR handlers exist) OnEvent handlers are registered with the
__registerOnEventHandler() function.

void __registerOnEventHandler(STATEPARAMS, void *handlerAddr,
                              OnEventTypeEnum evType);


All you need to do is pass the type of OnEvent you want to handle, and the
address of the handler. Then, when that OnEvent is triggered, BASIClib will
adjusts the stack, and transfer control to that address.

Adjusts the stack?

Let me clarify. Let's say you have a procedure, butts(), and it registers an
OnEvents handler. It then calls another procedure, which calls another one.
A couple of procedures down the line (and on the stack), one of the procedures
triggers an OnEvent.

+---------+
| proc3() | <---- OnEvent triggered here.
+---------+
| proc2() |
+---------+
| proc1() |
+---------+
| butts() |
+---------+

Control has to drop back into butts() for the handler, which means that we
need to adjust the stack and base pointers to where they were for butts().
This also means that if inside the handler, butts() makes a function call or
allocates any more stack space, proc1() (any maybe proc2 and proc3)'s stack
is overwritten. So, we need to protect that data, in case of a RESUME
statement, which will return us back to otherwise potentially destroyed data.
SO, we allocate memory, copy all the stack beyond butts() to that memory,
set the stack and base pointers back to butts(), and unconditionally jump into
it. If butts() returns from within the handler, we just free that protected
stack buffer, deregister the OnEvent handler, and continue as normal. If a
RESUME command of any type is reached inside the handler, the protected
stack buffer is copied back to the stack, the stack and base pointers are
restored to their pre-event state, and we unconditionally jump back into the
procedure that triggered the OnEvent.

After the OnEvent has been triggered, and before the handler is called, the
return address from butts() is read from the stack, and our cleanup
routine address is patched in, so we can cleanup even if butts() returns.
The stack is repatched from within the cleanup routine and the RESUME functions
with the original address, to prevent crashes.

Events are triggered like this:

void __triggerOnEventByType(STATEPARAMS, OnEventTypeEnum evType);

All you need to know is the type of OnEvent you want to trigger. To generate
a runtime error, you CAN do this (but shouldn't):

__triggerOnEventByType(STATEARGS, ONERROR);

Tada!

Actually, it's wiser to use __runtimeError(), which allows you to specify
what error you want to throw, and abstracts the interface somewhat.


For ON ERROR RESUME NEXT statements, the parser/compiler needs to generate
code like this:


BASIC:

SUB OPENFILE ()
    ON ERROR RESUME NEXT
    OPEN "FEH.TXT" FOR INPUT AS #1
END SUB


C:

void vbp_openfile(STATEPARAMS)
{
    __registerOnEventHandler(STATEARGS, &&l1, ONERROR);
    vbp_open(STATEARGS, "FEH.TXT", 1, INPUT);
    return;

l1:
    __resumeNext(STATEARGS);
}

(This has obviously been simplified for the purpose of illustration...)


Lastly, we've been very careful to always specify these as OnEvents, and not
merely events. These are not to be confused with the event-handling subsystem
of Visual Basic GUI. Those are VERY different.

/* end of on_events.txt ... */


