DDJ-Embedded, April 11, 2002
Bob Meets NUON

David Betz

Bob is a dynamic object-oriented language with syntax similar to C/C++, Java, and JavaScript - and it is used in NUON, a hardware/software platform for DVDplayers and set-top boxes.

David is a former technical editor for DDJ, and the author of XLisp and XScheme, among other languages. He currently is an engineer for VM Labs. He can be contacted at dbetz@xlisper.mv.com.


Bob is a dynamic object-oriented language with syntax similar to C/C++, Java, and JavaScript. It supports a prototype-based single-inheritance object model where there are no separate classes or instances. Every Bob object inherits behavior from another object and any Bob object can serve as the prototype for another Bob object. The prototype model makes it easy to create singleton objects, which are quite common in user interfaces. It is also a model that is familiar to JavaScript programmers.

NUON is a hardware/software platform for DVD players and set-top boxes (http://www.nuon-tech.com/). The hardware part is a powerful media processor with four general-purpose processors, as well as dedicated hardware to support efficient decoding of DVD program material including MPEG-2 video and AC-3 audio. One of the advantages of the NUON processor is that, because it is programmable, it is flexible enough to support new standards as they come along. Dedicated hardware solutions have a difficult time with this.

The software part is a robust collection of firmware supporting DVD, CD Audio, MP3 Audio, and VCD playback, along with a sophisticated Virtual Light Machine that can be customized to specific audio programs. It also supports the ability to play NUON-enhanced movies as well as NUON games. As of November 2001, there are three players on the market that incorporate NUON technology: the Toshiba SD-2300, the Samsung Extiva N-2000, and the Samsung DVD-N501.

What About Bob?

I first described the Bob programming language in the article "Your Own Tiny Object-Oriented Language" (DDJ, September 1991). Source code and related files for Bob are available at http://www.mv.com/ipusers/xlisper/ and from DDJ (see "Resource Center," page 5).

Like Java and JavaScript, Bob is a dynamic language. This means that the language run time manages memory and there is no need for you to keep track of when an object is no longer in use. The Bob garbage collector reclaims any memory that is no longer referenced by the program.

Listing One is a simple example of creating objects in Bob. The line Hazard = new Object(); creates a new object and assigns it as the value of the variable Hazard. The line define Hazard.initialize(cave) defines the initialize method that initializes objects created with the new operator. When you use the expression new object or new object(arguments), you create a new object that inherits from object. The new object is immediately sent the initialize message to initialize its state.

Listing One also references the this variable. As in C++ or Java, the this variable within a method refers to the object receiving the message that caused the method to be invoked. In addition, the initialize method must return this as its value because its value becomes the value of the new expression used to create the new object.

I recently had the opportunity to use Bob as the scripting language for a commercial product — the NUON DVD platform. I used Bob to script the UI and, in the process, I learned a bit about how to make it easier to interface Bob with application programs.

Embedding Bob in an Application

To use Bob in an embedded application, you must first create a Bob interpreter context; see Listing Two. The call to BobMakeInterpreter creates and initializes an interpreter context. You pass a pointer to this interpreter context to many of the Bob API functions.

The call to BobEnterLibrarySymbols adds the standard Bob functions and objects to the interpreter context. This includes a number of useful functions including LoadObjectFile.

The call to BobUseEval is optional. It lets Bob code call the functions Load, Eval, and CompileFile. All of these functions require the Bob compiler to translate Bob source code into Bob object code. Since Bob provides a standalone compiler to translate Bob source code into object code that can be loaded by the run-time function LoadObjectFile, it isn't necessary to have the compiler available at run time. The only reason for including the compiler would be to let users type Bob code to be evaluated at run time. This is useful for debugging, but generally not necessary in production code.

The errorHandler field of the interpreter context points to an application-specific function for handling Bob run-time errors. It is important for an application to be able to take control when an error occurs so that it can perform application-specific recovery before attempting to continue.

The protectHandler field points to an application-specific function to protect Bob values from being garbage collected. It is sometimes useful to be able to store Bob values in application-specific data structures that are not automatically scanned by the Bob garbage collector. This handler provides a way for the application to protect these values from the garbage collector.

The standardInput, standardOutput, and standardError fields point to application-specific I/O streams. A stream is a generalized way of handing input and output. Each stream provides functions for reading and writing characters, as well as closing the stream. Listing Two is a simple implementation of a console stream that uses the Standard C Library to read/write characters from the console.

Adding Application-Specific Objects

Part of using Bob in an embedded application is creating new application-specific objects. Bob supports a data type called BobCPtrObject. Instances of this type behave like objects in the sense that they can respond to messages and have properties. The difference is that the methods of a BobCPtrObject are written in C instead of in Bob, and the values of the properties of a BobCPtrObject are accessed through C getter and setter functions. Listing Three shows how to define a new BobCPtrObject type. The function BobEnterCPtrObjectType adds a new type to the Bob interpreter context specified as its first argument. The second argument specifies the BobCPtrObject that the new type inherits from. It can be NULL if the new type is a root type. The third argument is the name of the new type. Once this call completes, a new symbol will be defined in the global scope with this name. The last two arguments point to tables of methods and properties of the new type.

Methods

Listing Four shows an example of a method written in C. The call to BobParseArguments parses the arguments passed in the method call and places them in C variables. The first argument is a pointer to the Bob interpreter context. The second is a string describing the argument types. Like the format string in the C printf function, each argument description is paired with actual arguments that follow the descriptor string. The first argument is always the value of the this variable. The second argument is the value of the _next variable. In Listing Four, the characters "p=" mean that the first argument must be a BobCPtrObject and that its type must be a subtype of nuiBobWidgetType. This is the type of all widgets in the NUON User Interface (NUI). The "*" matches any argument and causes the argument to be skipped over. The _next argument is not needed in this method. The "|" character means that any arguments that follow it are optional. In the C code, you should assign default values to the variables associated with these arguments before calling BobParseArguments, as they will not be assigned if their corresponding Bob arguments are left out of the method call. The "B" means that the corresponding argument is a Boolean value. The C variable associated with this argument should be an integer that will be assigned FALSE (0) if the Bob argument is nil or false, and TRUE (1) if it is anything else. In other words, Bob interprets every value other than nil or false as true.

The C method should return a Bob value. The Bob interpreter context provides a few useful values like trueValue and falseValue. Others can be constructed using Bob API functions like BobMakeInteger and BobMakeCString.

Properties

Listing Five is a property written in C. Bob handles properties using a pair of functions. When the value of a property is needed, Bob calls the getter function. This function is responsible for providing a value for the property. When Bob wants to set the value of a property, Bob calls the setter function. A setter function is not needed for a read-only property. In this case, NULL should be passed as the value of the setter function. The getter function gets the Bob interpreter context and the object whose property value is wanted. It should return the value of that property. The setter function gets the Bob interpreter context, the object whose property is to be set and the new property value.

The NUON Environment

The NUON User Interface is a simple application framework that provides a model similar to that of a web browser — where content is displayed in frames, and frames can contain other frames.

NML is a markup language for describing NUON menus. It is based on XML and provides tags for describing the widgets that make up a NUON menu. A widget can be used to display a value or to accept input from users. The NUI framework supplies many kinds of widgets for various purposes. Listing Six is an NML menu. The <nml> tag encloses the entire menu description. Each <textStyle> tag describes a text style and gives it a name. Each <script> tag causes a script to be loaded into the scope of the NML file. The <body> tag encloses the body of the menu that contains all of the widgets. Each <widget> tag describes a widget; and each <group> tag encloses a group of widgets that are related in some way (it is actually a special type of widget). Other types of widgets include text, which supports the display and input of text; slider, which shows the value of a numeric variable graphically as a bar with a length proportional to the value of the variable; select, which lets users select amongst a set of values; image, which displays a GIF image; and frame, which displays another NML file.

The NUI run-time environment defines a single top-level frame widget that is the value of the variable topFrame. An application begins by loading an NML file into that frame.

To refer to a widget in a Bob script, it must have a name attribute. When an NML file is loaded, a variable named widgets is created. Its value is an object with properties for each of the named widgets in the NML file. The value of each of these properties is a widget object. In order to keep the variables from each NML file from interfering with each other, a new variable scope is created when an NML file is loaded into a frame. Each of these scopes inherits from the global scope and so has access to all of the Bob global functions and objects (but it has its own private copies of variables like widgets). This prevents name clashes between NML files and their associated scripts. Bob API functions that refer to variables take a BobScope argument instead of a BobInterpreter argument to indicate which scope is being used.

Did you ever wonder how your DVD player knows what to do when you press the fast-forward button on your remote control? In the NUON player architecture, each button you press on the remote control generates an event that is dispatched to an appropriate event handler. On the N501, that event handler is a piece of Bob code. The handler for the fast-forward event checks to make sure that the fast-forward function is allowed in the current mode (you can't fast forward through the FBI warning) and issues a foreign function call to the DVD navigation subsystem to tell the movie to start scanning forward. Listing Six has a simple example of an event handler written in Bob. The line onClick= 'topFrame.LoadPage("pages/wumpus.npg")' says what to do when the widget named "start" has focus, and the user presses the enter key on the remote control.

The Samsung DVD-N501 is the first NUON-compatible DVD player with the ability to read CD-R media. It is this ability that makes the NUON Open Platform possible. With the tools provided by VM Labs (where I work), you can author a CD-R on a Windows PC that plays on the Samsung DVD-N501. The NUON SDK (http://www.dev.nuon.tv/) includes a C/C++ compiler based on the GNU C compiler as well as an assembler, linker, and other tools required to build NUON applications.

Conclusion

While assembler and C/C++ are the best way to produce a high-performance application for NUON, the NUON User Interface Toolkit provides an easy way to produce simple applications in a browser-like environment. Bob makes it easy to script these applications using a familiar JavaScript-like language. The Bob API also makes it easy to embed Bob in other applications that require a small and efficient scripting language.

DDJ

Listing One

///////////
// Hazard
Hazard = new Object();
define Hazard.initialize(cave)
{
    this.location = cave;
    cave.AddHazard(this);
    return this;
}
define Hazard.Move(cave)
{
    if (this.location != cave) {
        this.location.RemoveHazard(this);
        this.location = cave;
        cave.AddHazard(this);
    }
    return this;
}
///////////
// Pit
Pit = new Hazard;
define Pit.Bump(game)
{
    game.Over("YYYIIIIEEEE . . . Fell in pit!");
}
Back to Article

Listing Two

#include "bob.h"
static int CloseConsoleStream(BobStream *s)
{
    return 0;
}
static int ConsoleStreamGetC(BobStream *s)
{
    return getchar();
}
static int ConsoleStreamPutC(int ch,BobStream *s)
{
    return putchar(ch);
}
BobStreamDispatch consoleDispatch = {
     CloseConsoleStream, ConsoleStreamGetC, ConsoleStreamPutC
};
typedef struct {
    BobStreamDispatch *d;
} ConsoleStream;
ConsoleStream consoleStream = { &consoleDispatch };
BobInterpreter *InitBob(void)
{
    BobInterpreter *c;
    /* make the interpreter context */
    if (!(c = BobMakeInterpreter(HEAP_SIZE,0,STACK_SIZE)))
        return NULL;
    /* use the standard library */
    BobEnterLibrarySymbols(c);
    /* allow access to the compiler */
    BobUseEval(c);
    /* establish an error handler */
    c->errorHandler = ErrorHandler;
    /* protect bob values */
    c->protectHandler = ProtectHandler;
    /* setup standard i/o */
    c->standardInput = (BobStream *)&consoleStream;
    c->standardOutput = (BobStream *)&consoleStream;
    c->standardError = (BobStream *)&consoleStream;
    /* return the interpreter context */
    return c;
}
Back to Article

Listing Three

/* built-in methods */
static BobValue BIF_HideWidget(BobInterpreter *c);

/* built-in properties */
static BobValue BIF_WidgetX(BobInterpreter *c,BobValue obj);
static void BIF_SetWidgetX( BobInterpreter *c, BobValue obj, BobValue value);
/* 'Widget' methods */
static BobCMethod widgetMethods[] = {
BobMethodEntry( "Hide", BIF_HideWidget                 ),
/* more methods */
BobMethodEntry( 0,      0                              )
};
/* 'Widget' properties */
static BobVPMethod widgetProperties[] = {
BobVPMethodEntry(   "x",  BIF_WidgetX,    BIF_SetWidgetX ),
/* more properties */
BobVPMethodEntry(   0,    0,              0              )
};
/* NuiInitBobWidgets - initialize the 'Widget' object */
int NuiInitBobWidgets(BobInterpreter *c)
{
    /* make the 'Widget' type */
    if (!(nuiBobWidgetType = BobEnterCPtrObjectType( c, nuiEventTargetType,
                                "Widget", widgetMethods, widgetProperties)))
        return FALSE;
    /* return successfully */
    return TRUE;
}
Back to Article

Listing Four

/* BIF_HideWidget - built-in method 'Hide()' */
static BobValue BIF_HideWidget(BobInterpreter *c)
{
    int hideWidgetBackgroundP = -1;
    NuiWidget *widget;
    BobParseArguments(
        c, "P=*|B", &widget, nuiBobWidgetType, &hideWidgetBackgroundP);
    if (!widget) {
        TRACE("Operation attempted on inactive widget\n");
        return c->falseValue;
    }
    NuiHideWidget(widget);
    switch (hideWidgetBackgroundP) {
    case TRUE:
        NuiHideWidgetBackground(widget);
        break;
    case FALSE:
        NuiShowWidgetBackground(widget);
        break;
    }
    return c->trueValue;
}
Back to Article

Listing Five

/* BIF_WidgetX - built-in property 'x' */
static BobValue BIF_WidgetX( BobInterpreter *c, BobValue obj)
{
    NuiWidget *widget = (NuiWidget *)BobCObjectValue(obj);
    if (!widget) {
        TRACE("Operation attempted on inactive widget\n");
        return c->nilValue;
    }
    return BobMakeInteger(c,PageFileSwap16(widget->pageFileWidget->x));
}

/* BIF_SetWidgetX - built-in property 'x' */
static void BIF_SetWidgetX( BobInterpreter *c, BobValue obj, BobValue value)
{
    NuiWidget *widget = (NuiWidget *)BobCObjectValue(obj);
    if (widget) {
        if (BobIntegerP(value))
            widget->window.rect.x = BobIntegerValue(value);
        else
            ConsolePrintF("Expecting integer value\n");
    }
    else
        TRACE("Operation attempted on inactive widget\n");
}

Listing Six

<nml>
<textStyle name='welcome'
           font='fonts/system.t2k'
           color='black'
           size='30'/>
<textStyle name='prompt'
           font='fonts/system.t2k'
           color='goldenrod'
           size='24'/>
<script src='scripts/wumpus.bbo'/>
<body width='720' height='480' bgColor='peachPuff' start='start'>
<group x='20' y='10'>
<widget type='text'
        textStyle='welcome'
        x='80'
        y='40'
        width='250'
        height='30'
        align='left'
        value='Welcome to Hunt the Wumpus!'/>
<widget name='pic'
        type='image'
        src='images/wumpus.gif'
        x='80'
        y='100'/>
<widget name='start'
        type='text'
        highlightTextStyle='prompt'
        x='80'
        y='300'
        width='250'
        height='24'
        align='left'
        onClick='topFrame.LoadPage("pages/wumpus.npg")'
        value='Press Enter to Start the Game...'/>
</group>
</body>
</nml>