Dictionaries

To facilitate command parsing, TADS 3 provides an intrinsic class called "Dictionary."  A Dictionary object stores associations between "keys" and objects, and can be efficiently searched for a key to yield all of the objects associated with the key.

 

A "key" is a combination of a string and a property ID.  The Dictionary class includes a property ID with each key so that object associations can be differentiated by type; each property is a type of association.  In practical terms, the property used in a dictionary key will usually be a vocabulary property, such as "noun" or "adjective."   A given word might be used with some objects as a noun, but with other objects as an adjective; for example, "card" might be used as a noun with an "ID Card" object, but as an adjective with a "card slot" object.  The Dictionary class differentiates by property ID to allow for searching a Dictionary object for the objects matching a word in a particular usage context.

 

The TADS 3 compiler has built-in support for the Dictionary intrinsic class.  (This built-in support is part of the compiler, not the interpreter; for the interpreter, a Dictionary object is the same as any other.)  The compiler support is provided by two new statements, dictionary and dictionary property, and by recognition of dictionary properties when defined in object property lists.

 

The dictionary statement has two purposes.  First, it defines a new object instance of class Dictionary, if the name has not been previously defined.  Second, it establishes the active dictionary, which is the dictionary into which the compiler will insert each dictionary word defined with an object.

 

The dictionary property statement tells the compiler that a given property is henceforth a dictionary property.  This means that, whenever the property is subsequently used in an object definition, the property defines dictionary words for the object.  The words are entered into the active dictionary under the given property.

 

Here's some sample code that demonstrates how these statements work.

 

// create a new dictionary and make it active
dictionary myDict;
 
// establish the dictionary properties
dictionary property noun, adjective, plural;
 
// define an object
redBook: Book
    noun = 'book' 'booklet'
    adjective = 'red'
;

 

The effect of these new statements makes the code needed to define an object with vocabulary words very similar to the way it looked in TADS 2.  In particular, note that dictionary properties accept the special implied list syntax that TADS 2 used for vocabulary words: there is no need to enclose a vocabulary property's string list in square brackets.  In fact, the TADS 3 compiler doesn't even allow list notation for dictionary properties.

 

Even though the code looks similar to the way it did in TADS 2, though, there are some important differences.

 

First, the noun and adjective properties at run-time behave like normal properties: the compiler sets the values of these properties to simple string lists containing the strings actually defined, plus any strings inherited from superclasses.  You can use and modify these properties during program execution just like any other; however, note that, because these properties are nothing special to the interpreter, changing them has no effect on the dictionary.

 

Second, the dictionary object created by the dictionary statement is a full-fledged object at run-time.  The object is an instance of the intrinsic class Dictionary, which provides the following methods:

 

findWord(str, &voc_prop) – search the dictionary for the given string and property ID.  Returns a list of all of the matching objects; if there are no objects, returns an empty list.

 

findWordTrunc(str, &voc_prop) – search the dictionary for truncated matches for the given string and property ID, returning a list of the truncated matches only.  A word in the dictionary matches if the dictionary word is longer than the given string, and the given string is at least as long as the current truncation length (set with the setTruncLen() method), and the given word is a prefix of the dictionary word.  For example, if str is 'flashl' and the current truncation length is 4, this method will match the word 'flashlight', because it matches str exactly up to the length of str, but not 'flashbulb', because 'flashl' is not a prefix of 'flashbulb'—this is true even though the truncation length is only 4, because the full length of str must match no matter what the truncation length is set to.  Note that the result list does not include any exact matches—the result list includes only truncated matches.

 

setTruncLen(len) – sets the truncation length for the dictionary.  When findWordTrunc() is called, it uses the truncation length set with this method.  Each dictionary created with the compiler's dictionary statement has an initial truncation length of 6; you can use this method if you want to set a different truncation length.

 

addWord(obj, str, &voc_prop) – add an object to the dictionary with the given string and property key.  The argument str can be a string value, or can be a list of strings; if str is a list of strings, the result is the same as calling addWord() for each string in the list.  If the word association to be added is already defined (i.e., another entry with the same string, object, and property already exists), the new association is simply ignored.

 

removeWord(obj, str, &voc_prop) – removes from the dictionary the object's association with the given string and property key.  Only the specific association of object, string, and property is removed; if the same object is also associated with other strings, the object is not removed from those other associations, and likewise if the same string and property are associated with different objects, those object associations are not removed.  str can be a string value or a list of strings; if it's a list of strings, the result is the same as calling removeWord() for each string in the list.  If the word association to be removed is not defined, the operation is simply ignored.

 

isWordDefined(str) – searches the dictionary for the given string and determines if it's associated with any objects.  If the word occurs in the dictionary at all, this function returns true; otherwise, it returns nil.

 

isWordDefinedTrunc(str)  - searches the dictionary for truncated matches to the given string.  Returns true if the string matches any truncated dictionary words, nil if not.  This function uses the same truncation rules as findWordTrunc(), and like findWordTrunc(), this function does not consider any exact matches, but only considers truncated matches.

 

There are a few other interesting features of dictionaries to note.

 

Dictionaries and garbage collection:  A dictionary references its objects "weakly."  This means that adding an object to a dictionary does not prevent the garbage collector from deleting the object.  If an object is referenced in a dictionary, but the object becomes otherwise unreachable, the garbage collector will delete the object, and will at the same time remove all of the object's associations from the dictionary.  While this might seem strange at first glance, it is actually very useful, because it means that you don't have to worry about manually deleting dictionary references to objects that have become unneeded.

 

Creating dictionaries dynamically:  You can create a dictionary object at run-time using the new operator.  Note that, in order to do this, you must define the Dictionary intrinsic class to the compiler, which you would normally accomplish simply by including the dictionary system header file.

 
// include the system header defining the Dictionary class
#include <dict.h>
 
myFunc()
{
    // create a new dictionary with truncation length 9
    local dict = new Dictionary(9);
 
    // add some words
    dict.addWord(redBook, 'red', &adjective);
    dict.addWord(redBook, 'book', &noun);
}

 

Using multiple dictionaries in the compiler:  You can use multiple dictionaries at compile-time simply by using a new dictionary statement for each dictionary.  You can switch back to an existing dictionary with another dictionary statement naming the original dictionary.  The dictionary statement establishes the active dictionary, which remains in effect until the next dictionary statement.  Multiple dictionaries might be useful in certain situations, such as when you want to create different parsing modes, each having their own separate vocabulary words.

 

Dynamic object creation and dictionaries:  When you create a new object with operator new, the interpreter will not automatically add any vocabulary to any dictionary for the object.  While this might seem a deficiency, remember that the interpreter doesn't think of dictionary objects as anything special, so it doesn't have any idea that you might want some random object creation to have a side effect on a completely unrelated object (such as a dictionary).  This is a cost of the more general and flexible approach that TADS 3 uses.  However, in most cases you will probably not notice this lack of automatic action, because libraries should be able to handle this more or less transparently through inheritance.  In particular, most libraries will probably want to put something like this in their lowest level classes:

 

class Thing: object
    construct()
    {
        // add all of my vocabulary to the default dictionary
        libDict.addWord(self, noun, &noun);
        libDict.addWord(self, adjective, &adjective);
        libDict.addWord(self, plural, &plural);
    }
;

 

As long as each class derived from Thing (or from subclasses of Thing) properly inherits its superclass constructor from its own constructor, the library code will take care of adding the vocabulary words for the new instance, taking advantage of the accessibility of the dictionary property values that TADS 3 provides.