Callable Bob
by David Betz

Last fall I presented a version of my Bob programming language
that was extended to support an object store designed for use as
the basis for building an online conferencing system.  While
that Bob interpreter was easy to extend with additional built in
functions, it was essentially a standalone interpreter.  Bob was
the main program and extensions were called as subroutines. 
This works well in may applications but falls short when trying
to use Bob as an extension language for an existing application.

To solve this problem, I designed a version of Bob that runs as
a Windows Dynamic Linked Library (DLL).  Along the way, I
separated Bob into several modules so that each could be used
independantly.  The memory manager is now an independant module
that can be used as the basis for other languages needing a heap
with automatic garbage collection.  I've also separated the Bob
interpreter from the Bob compiler since some applications only
need to run already compiled code.  In fact, I've separated the
runtime library from the rest of the interpreter so it is
possible to run programs that only need the intrinsic functions
without any library at all.  To make things simpler, I've
included all of these modules in the Bob DLL even though they
are logically separate.

One problem with Windows DLL's is that they have only a single
data segment even though several applications may be linked to
them at the same time.  This was a problem since Bob had many
global variables most having to do with the memory manager.  My
first step in turning Bob into a callable library was to move
all of the globals into context structures and add a parameter
to every function to explicitly pass in the appropriate context.
I created two context structures, the interpreter context which
contains the bytecode interpreter variables as well as the
memory manager variables and the compiler context which contains
the compiler and scanner variables.  The compiler context also
points to an interpreter context so that the compiler has access
to the memory manager for creating objects.

Because the compiler and interpreter context is passed into each
function explicitly, it is now possible to create more than one
context at a time.  This could allow a multi-threaded program to
have multiple threads all executing Bob programs independantly. 
It also means that several programs linked to the same Bob DLL
can operate without interfering with each other.

Now I'll show how to invoked the Bob DLL to create a simple
read/eval/print loop for Bob expressions.  First, it is
necessary to create an interpreter context:

InterpreterContext *ic = NewInterpreterContext(16384,1024);
ic->errorHandler = ErrorHandler;

The first parameter to is the size of the heap and the second is
the size of the stack.  The second line sets up an error
handler.  Bob will call this error handler whenever an error
occurs passing it an error code and relevant data.

If you need access to the runtime library functions, that is
arranged by:

EnterLibrarySymbols(ic);
ic->findFunctionHandler = FindLibraryFunctionHandler;

The second line sets up a handler that the interpreter will call
to get the address of a function handler given a function name. 
This is necessary when the interpreter restores a saved
workspace because the saved workspace format on disk only
contains the names of library functions, not their addresses. 
This is to allow saved workspaces to work correctly even after
the DLL has been rebuilt causing the function handler addresses
to change.

Then it is necessary to initialize the compiler:

CompilerContext *c = InitCompiler(ic,4096,256);

This creates a compiler context with the specified interpreter
context.  The numeric parameters are the size of the compiler
bytecode staging buffer and the size of the literal staging
buffer.

The Bob memory manager is a compacting stop and copy garbage
collector and can change the address of objects when a garbage
collection occurs.  Because of this, it is necessary for the
memory manager to know about all variables that could contain a
pointer to an object in the heap.  The variables that the
interpreter uses are contained within the interpreter context
structure and so can be located by the memory manager.  However,
it is sometimes useful for a client of the memory manager to
maintain its own pointers into the heap.  The Bob memory manager
allows for this by providing the function ProtectPointer to
register an object pointer with the memory manager:

ObjectPtr val;
ProtectPointer(ic,&val);

This registers the specified pointer with the memory manager and
guarantees that its value is updated whenever the garbage
collector moves the object it points to.

This leaves the read/eval/print loop itself.  It looks like this:

for (;;) {
    printf("\nExpr> ");
    if (gets(lineBuffer)) {
        Stream *s = CreateStringStream(lineBuffer,
                                       strlen(lineBuffer));
        if (s) {
            val = CompileExpr(c,s);
            val = CallFunction(ic,val,0);
            printf("Value: ");
            PrintValue(ic,val,ic->standardOutput);
            CloseStream(s);
        }
    }
    else
        break;
}

Bob does all of its I/O through what it calls streams.  A stream
is an object possible with some data and a pointer to a dispatch
table.  The dispatch table has pointers to handlers to carry out
various stream operations.  At the moment, there are handlers
for getting and putting characters and a handler for closing the
stream.  The call to CreateStringStream creates a stream that
will allow the Bob compiler to read characters from the string. 
The interpreter context structure contains pointers to the
standard I/O streams that must be setup by the client of the Bob
DLL.  These streams should arrange for characters to be read and
written to the standard input and output of the application.

The call to CompileExpr compiles a single Bob expression and
returns a compiled function that when called with no arguments
will cause the expression to be evaluated.

The function CallFunction calls a function with arguments.  The
general prototype is:

ObjectPtr CallFunction(InterpreterContext ic,
                       ObjectPtr function,
                       int argumentCount,...);

The arguments after argumentCount are the arguments to be passed
to the specified Bob function.  They are of type ObjectPtr (a
pointer to a Bob heap object) and there should be as many of
them as argumentCount indicates.

You can also call a Bob function by name using the function:

ObjectPtr CallFunctionByName(InterpreterContext ic,
                             char *functionName,
                             int argumentCount,...);

Of course, there are many other functions in the Bob DLL.  It
contains a full compliment of object creation and access
functions for creating and manipulating objects of type
ObjectPtr as well as functions to control the interpreter.

This is just my first step in making Bob easier to embed in
applications.  My future plans include extending the Bob
language to support full function closures and optional
arguments and adding a "fast load" format for storing
precompiled Bob code in disk files.  This would make it possible
to distribute Bob functions without having to include the source
code, a necessary feature for using Bob to build commercial
applications. 
