Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

An Online Conferencing System Construction Kit


SP 94: An Online Conferencing System Construction Kit

David, the author of XLisp, XScheme, AdvSys, Bob, and many other programming systems, is a DDJ contributing editor. He can be contacted through the DDJ offices.


A few years ago, I presented Bob, a language with a C-like syntax and an object system that looked a bit like C++, in the article, "Your Own Tiny Object-Oriented Language" (DDJ, September 1991). Bob is a dynamic language with automatic storage management and an interactive interface. It is written in ANSI C and has been ported to several platforms, including the Macintosh, MS-DOS, and UNIX.

Recently, I decided to build an extensible computer-conferencing system, and Bob looked like a good choice for an embedded language. One advantage of inventing your own language is that you don't need anyone else's permission to change it. The basic Bob syntax (see Table 1) seemed like it would work well, but I needed a new object system. I decided to go with an object system I had first used in AdvSys, a language designed for writing text adventure games. (See "Dave's Recycled OO Language," DDJ, October 1993). This became the object system for the new version of Bob. The complete source code for this conferencing-enhanced implementation is available electronically; see "Availability," page 3.

The Bob Object System

If you have spent any time using computer-conferencing systems, you know that one of the primary types of data objects is the message. A message usually has an author, a subject, and some text. It may also have a creation date, a reference to another message if it is a reply, or various other data, all of which generally takes the form of a list of tags like "subject" or "author" and values like "Hiking" or "Anne." In fact, a message can be thought of as a collection of tag/value pairs, the text itself being the value of a tag such as "text."

This maps well onto the new Bob object model. In Bob, all objects consist of a collection of properties. A property has a name and a value. An object can inherit from another object. In this case, the object it inherits from is called its "class." There really isn't a distinction between classes and objects in Bob, though. A class is just another object with its own collection of properties.

The only operations supported on objects are getting and setting property values. To get the value of a property, you use an expression like message.subject. If message is a variable whose value is an object, this expression will get the value of the subject property of that object. If the object doesn't have a subject property, Bob will look for a subject property in the class of the object. This search continues either until a property is found, or until an object is found with no parent class.

Setting a property works in a similar way, as in, message.subject = "Swimming;". There is a difference, though. When setting the value of a property, Bob only considers the properties local to the specified object. There is no search up the inheritance chain. If Bob doesn't find the property in the specified object, it adds a new property to the object with the specified value. This allows an object to share values with other objects that have the same class, but also to override those values when necessary.

A conferencing system isn't much good if all of the messages have to fit in memory all the time. Bob solves this by providing an object store so that objects can reside on disk. Updates to the object store are protected by file locking so that multiple users can access the same store at the same time.

The Bob object store consists of a collection of numbered slots where data can be stored. Each slot is a variable-length stream of bytes. Figure 1 shows the Bob functions for accessing an object store. The functions for creating, opening, and closing object stores are fairly straightforward.

The CreateObject function creates a new slot and returns a stream you can use to write data into the slot. Once you're done writing the slot data, you use the Close function to close the stream.

To read from an existing slot, use the OpenObject function. It opens the specified object and version and returns a stream you can use to read data from the slot. The version parameter allows you to specify which version of the object you want to open. Specifying 0 gets the most recent version. Specifying a negative number gets older versions relative to the most recent version; for instance, --1 will get the next to last version. Specifying a positive number gets that version. Version numbers always start with 1.

To create a new version of an object, use the UpdateObject function, which creates a new version of the specified object and returns a stream you can use to write the object data just like the stream returned by CreateObject. You can use ObjectNumber and ObjectVersion to determine the object number or version number of an object opened by OpenObject, CreateObject, or UpdateObject.

The cursor functions provide a means of iterating through the slots in an object store. A cursor points to a particular slot. The slot it points to can be changed by SetCursorPosition, GetNextObject, and GetPreviousObject. The current slot can be opened with OpenCurrentObject.

This, in itself, would probably be sufficient to build a conferencing system. Each conference would be a separate object store. Each message would be stored in a slot in the object store. The message would consist of a few lines at the start with the header fields like "author" and "subject" followed by the text of the message.

As you probably guessed, I called these "object stores" because I intend to store more than just text in them. Bob provides a facility for writing a representation of any Bob data type to a stream that can be reconstructed later. This process is called "flattening" and is recursive. Flattening an object writes out each property tag and value as well as the class of the object. Of course, the values of properties can themselves be objects, vectors or any other Bob data type.

The class of an object is handled a little differently. Calling the flattener recursively to write out an object's class would cause that class object (and its class, and so on) to be written out every time an object with that class was flattened. This would waste disk space and cause problems when the objects were unflattened, since objects that shared the same class would end up with separate but identical classes when read back in. This would destroy any data sharing between objects of the class.

To get around this problem, Bob requires that all objects used as classes contain a className property. When the flattener writes out an object, it writes out the value of the className property of its class object instead of the class object itself. When the object is unflattened, the value of the global variable with that name is used to initialize the class field of the unflattened object. This means that every class object should be stored as the value of the global variable with the same name as its className property value.

Since any Bob data type can be flattened to a stream and since object-store slots are streams, any Bob data type, for instance, an object, can be written to an object-store slot. In fact, several objects can be written to the same slot.

I've implemented messages as Bob objects with properties for each of the fields of the message. This makes it very easy to create and manipulate messages in Bob. For instance, you can create a message using the code in Example 1(a), and then write this message to an object store using the code in Example 1(b). Finally, Example 1(c) lets you read it back. The flattening facility makes it very easy to read and write messages without getting into a lot of complicated parsing of the header fields.

This may, however, turn out to be too simple an approach to storing messages. Most messages will have text considerably longer than that in Example 1, making it impractical to store the text as the value of one of the properties of the message object. This is where the ability to write more than one flattened data object into an object-store slot can be handy. You can split the message into a header part and a text part. The header can be stored as an object just as I have described, while the text could be stored following the header object as either a string or as a sequence of bytes, using the PutC function; see Figure 2. This would allow the header to be read quickly to display a summary of the message without having to read the potentially large text part.

Since the flattening functions read and write from streams, you can also write Bob objects to regular files. This could be used to keep information about conferences like the moderator, a description, membership lists, and access restrictions. Files with flattened objects could also be used for user information like passwords, lists of conferences the user has joined, addresses, phone numbers, and personal comments.

E-Mail

Most conferencing systems support electronic mail as well as message databases. This can be done with Bob by using a single object store, the Mail store, to hold all of the e-mail messages in the system and by using one object store per user Inbox to hold references to the messages received by that user. The references are just the object number in the mail object store of the message received. Why don't I just store the messages themselves in the user's mailbox? Because a mail message might be addressed to several recipients. If the message itself is stored in each user's mailbox, the data for that message will be duplicated for each user. Putting the message in a common mail store and putting a reference in the user's mailbox solves this problem and allows all recipients to share the same copy of the message. The message reference also provides a handy place to keep other information, such as whether the message has been read or not.

Distributed Conferencing

It is often desirable to distribute conferences across a number of machines. This makes it possible to place machines near to where users will be accessing them so that they are accessible by a local phone call. In order to distribute a conference across multiple machines, it is necessary to distribute the messages posted to that conference, as well. The flattening facility of Bob makes this fairly easy. Since a message (or any object) can be flattened to an arbitrary output stream, there is no reason it can't be flattened to a communications link to another computer. On the other end, the unflattener would reconstruct the object ready to be placed in the parallel conference on the target machine. This could be done to forward e-mail messages as well.

Active Messages

So far, I've only talked about fairly traditional text messages. By taking advantage of Bob's object-oriented nature, it's easy to allow for different types of messages with different behaviors. Assume that whenever you display a message, you do that by sending a displayMessage message to the message object. For text messages, the method for handling the displayMessage message would simply display the text. However, you could also invent a new type of message like a FormMessage, where the displayMessage method would display a form, allow the user to fill in fields in the form, and then automatically construct a response to the form message and send it back to the sender of the original message.

Beyond just having several built-in classes of messages like TextMessage and FormMessage, it would be possible for any message to carry with it its own method for the displayMessage message. This is because any object can override any method with its own version of that method and because the flattener can flatten any Bob data type, including a compiled method. Because Bob compiles methods to machine-independent bytecodes, these compiled methods can be run on any machine that can run Bob. You can write a message on a Macintosh that includes its own displayMessage method and send it to an MS-DOS user, and it will execute just fine.

Command Handling

It doesn't do much good to be able to store messages if it's impossible to read them. Any conferencing system needs some sort of user interface. I know it's the fashion these days to have fancy GUIs for everything, but I've chosen to initially use a text-based interface.

Bob contains a number of functions to support parsing commands, which are summarized in Figure 3. A line buffer is simply an array of characters with a pointer to the next character to fetch. The FillLineBuffer function reads characters from the specified stream and places them in the line buffer until it encounters an end-of-line character and sets the buffer's pointer to the first character of the line. The NextToken function returns the next space-delimited sequence of characters starting at the buffer pointer after skipping any leading spaces. The RestOfLine function returns the rest of the characters in the buffer, starting at the pointer after skipping leading spaces. The IsMoreOnLine function is a predicate that returns TRUE if there are more nonspace characters left in the buffer. These functions make it easy to write the simple command parsers used in text-based conferencing systems.

To handle a command, you must associate a command keyword with a function for carrying out the actions indicated by the command. Bob does this by using string addressing of object properties. A command table is simply an object where each property is a command name, and the property value is a method for carrying out the command. Example 2, for instance, points out another feature of Bob. Property names can be any Bob data type. In this case, we are using strings as property names because NextToken returns a string; this makes it easy to find the method for handling the command indicated by the string token.

Conclusion

Bob is still very much under development. Some of the features I'm considering for future versions are ways of handling the flattening of circular structures, distributed data storage on a LAN, using an object-store server, and a graphical interface. I'll probably add compressed bit vectors as a Bob data type to support the tracking of which messages a user has read in a conference. With an index facility for object stores, messages threads should be easier to implement. There's lots of room for improvement.

You may have noticed that Bob is not so much a conferencing system as a language for writing conferencing systems. It provides basic programming-language features along with a facility for the persistent storage of objects and some simple command-parsing functions. With these basic tools, you should be able to build your own conferencing system tailored to your specific needs.

Table 1: Bob syntax: (a) function definition; (b) formal arguments; (c) method definition; (d) formal argument list; (e) block; (f) local declaration; (g) local variable; (h) statement; (i) expression; (j) property expression; (k) message-sending expression; (l) literal function; (m) literal symbol; (n) literal list; (o) literal vector; (p) literal object; (q) Lvalue; (r) global variables; (s) run-time routines.

<B>(a)   </B>define <function-name>( <formal-arguments> )
      <block>

<B>(b)</B>   <argument-name> [ , <argument-name> ]...

<B>(c)</B>   define '[' <object> <keyword> ']'
      <block>
      define '[' <object> [ <keyword> :<argument name> ]...']'
      <block>

<B>(d)   </B><argument-name> [ , <argument-name> ]...

<B>(e)</B>   { [ <local-declaration> ]... <statement>... }

<B>(f)</B>   local <local-variable> [ , <local-variable> ]... ;

<B>(g)</B>   <variable-name> [ = <initial-value> ]

<B>(h)</B>   if ( <test-expression> ) <then-statement> [ else <else-statement> ] ;
      while ( <test-expression> ) <body-statement>
      do <body-statement> while ( <test-expression> ) ;
      for ( <init-expression> ; <test-expression> ;<increment-expression> )
      <body-statement>
      break ;
      continue ;
      return [ <result-expression> ] ;
      [ <expression> ] ;
      <block>
      ;

<B>(i)</B>     <expression> , <expression>
        <lvalue> = <expression>
        <lvalue> += <expression>
        <lvalue> --= <expression>
        <lvalue> *= <expression>
        <lvalue> /= <expression>
        <lvalue> %= <expression>
        <lvalue> &= <expression>
        <lvalue> |= <expression>
        <lvalue> ^= <expression>
        <lvalue> <<= <expression>
        <lvalue> >>= <expression>
        <test-expression> ? <true-expression> : <false-expression>
        <expression> || <expression>
        <expression> && <expression>
        <expression> | <expression>
        <expression> ^ <expression>
        <expression> & <expression>
        <expression> == <expression>
        <expression> != <expression>
        <expression> < <expression>
        <expression> <= <expression>
        <expression> >= <expression>
        <expression> > <expression>
        <expression> << <expression>
        <expression> >> <expression>
        <expression> + <expression>
        <expression> -- <expression>
        <expression> * <expression>
        <expression> / <expression>
        <expression> % <expression>
        -- <expression>
        ! <expression>
        ~ <expression>
        ++ <lvalue>
        -- <lvalue>
        <lvalue> ++
        <lvalue> --
        <expression> ( [ <arguments> ] )
        <expression> . <property-expression>
        <expression> '[' <expression ']'
        <message-sending-expression>
        ( <expression> )
        <variable-name>
        <literal-function>
        <literal-symbol>
        <literal-list>
        <literal-vector>
        <literal-object>
        <number>
        <string>
        nil

<B>(j)  </B> <symbol>
      <expression>

<B>(k)  </B> '[' <object> <keyword> ']'
      '[' <object> [ <keyword> <argument> ]...']'

<B>(l)  </B> function [ <name-string> ] ( <formal-arguments> ) <block>

<B>(m)  </B> \ <symbol-name>

<B>(n)  </B> \ ( [ <expression> ]... )

<B>(o)  </B> \ '[' [ <expression> ]... ']'

<B>(p)</B>   \ { <object> [ <property-name> : <expression> ]... }

<B>(q)   </B><variable-name>
      <vector-or-string-expression> '[' <expression> ']'
      <expression> . <property-expression>

<B>(r)</B>   stdin    The standard input file pointer
      stdout   The standard output file pointer
      stderr   The standard error file pointer

<B>(s)     </B>obj = New(class);
        obj = Clone(object);
        list = List(element,...);
        value = Cons(car,cdr);
        car = Car(cons);
        SetCar(cons,car);
        cdr = Cdr(cons);
        SetCdr(cons,cdr);
        symbol = Intern(printName);
        class = Class(object);
        value = GetLocalProperty(object,propertyName);
        Print(value[,stream]);
        Display(value[,stream]);
        DecodeMethod(method[,stream]);
        stream = FOpen(name,mode);
        Close(stream);
        ch = GetC(stream);
        PutC(ch,stream);
        Load(filename);
        Quit();

Figure 1: Object-store functions.

CreateObjectStore(name);
store = OpenObjectStore(name);
CloseObjectStore(store);
count = ObjectCount(store);

stream = OpenObject(store,objectNumber,versionNumber);
stream = CreateObject(store);
stream = UpdateObject(store,objectNumber);
DeleteObject(store,objectNumber);
objectNumber = ObjectNumber(stream);
versionNumber = ObjectVersion(stream);

cursor = CreateCursor(store,objectNumber);
CloseCursor(cursor);
SetCursorPosition(cursor,position);
objectNumber = GetNextObject(cursor);
objectNumber = GetPreviousObject(cursor);
stream = OpenCurrentObject(cursor,versionNumber);

Figure 2: Bob flattening functions.

Flatten(value,stream);
value = Unflatten(stream);

Figure 3: Bob parsing functions.

buffer = NewLineBuffer()
FillLineBuffer(buffer,stream);
token = NextToken(buffer);
line = RestOfLine(buffer);
more = IsMoreOnLine(buffer);

Example 1: (a) Creating a message; (b) writing a message to an object store; (c) reading back the message.

<B>(a)</B>   myMessage = New(Message);
      myMessage.author = "Jonathan";
      myMessage.subject = "Hiking";
      myMessage.text = "We went on a nice hike yesterday.";

<B>(b)</B>   myStore = OpenObjectStore("Messages");
      stream = CreateObject(myStore);
      Flatten(myMessage,stream);
      Close(stream);

<B>(c)</B>   myStore = OpenObjectStore("Messages");
      stream = OpenObject(myStore,1,0); // message one, current version
      myMessage = Unflatten(stream);
      Close(stream);

Example 2: Bob command table.

topMenu = \{ Menu
             "help": function() { Display("No help available.\n"); },
             "bye":  function() { Display("Sorry to see you go.\n"); }
           };


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.