TADS 3 Services Pack

 

 

User’s Guide

 

Version 1.0.2

 

 

 

 


Contents

 

Services Overview_ 4

Collection Methods 5

List Methods 5

Vector Methods 6

LookupTable Methods 7

Conditional and Case Functions 8

The Conditional Function_ 8

The Case Function_ 8

Delegate Object 10

Flags 12

BitFlag Class 13

Creating Bit Flags 13

DefBitFlag Macro_ 13

Flag Class 13

Flag Validation_ 14

Creating Flags 14

Converting Between Bit Strings and Flags 15

Setting and Clearing Flag Bits 16

Operating on Bits 16

Using Bitwise Methods 17

Using Shift Methods 18

Evaluating Flags 18

Comparing Flags 19

Counting Bits 19

Inheritance Order 21

Inheritance Structure List 21

Inheritance Order List 22

Next Inheritance Order 22

The inheritedobj Macro_ 23

Inheritance Order Property List 23

Inheritance Order State Structure List 24

Inheritance Order Defined Structure List 24

The createClone() Alternatives 25

The TspSimObjectCvtr class 26

Method Overloading_ 28

Module Registry 29

The PlaceHolder class 29

The RegisterModule and RequiresModule() Macros 29

Conditions for Using Module Registration_ 30

The ModuleDependency class 30

Output 32

Prototype Methods 33

The Object Model 33

Traits 33

Object Derivations 33

The new Operator vs. createInstance() 34

Traversing the Inheritance Hierarchy 34

Intriguing Aspects of TADS Object Model 34

Types of Objects 36

Classes 36

Ancestors 36

Descendants 36

First Ancestors 38

First Symbolic Ancestors 38

First Named Ancestors 38

First Descendants 39

First Named Descendants 39

PseudoGlobals 40

Regular Expression Token Matching_ 41

Symbols 42

The Symbols Class 42

Retrieving the Symbol for a Value_ 42

Retrieving the Single-quoted String for a Value_ 42

Checking for the Presence of a Key in the Global Symbols Table_ 42

Objects without Symbolic References 43

Unnamed Internals 44

Tokenizer Token Synchronization_ 46

Vector Methods 47

 

 

 


Services Overview

The TADS 3 Services Pack is a set of unrelated modules that provide useful services in the development of library and library extension modules. These services generally extend the basic TADS 3 language and are meant to be library independent.

 

 

 

 

 

 

This file is part of the TADS 3 Services Pack

Copyright © 2005 Kevin Forchione. All rights reserved.

 

 

 


Collection Methods

The methods in this module extend the functionality of Collection class objects.

 

List Methods

These methods extend the function of the List Class. Many of these methods have been added to allow for polymorphism with Vector class objects.

 

appendAll(val)

This works like append(val), except that if val is a List or Vector, each element of val is individually appended to the the list value.  This method returns a new list.

 

removeElement(val)

Deletes one or more elements from the list; each list element whose value equals val is removed from the list.  This reduces the length of the list by the number of elements removed.  If there is no element of the list whose value equals val, the list is unchanged.  Returns a new list.

 

firstElm()

Returns the first element of the list.  If the list has no elements, returns nil.

 

Identical to car().

 

lastElm()

Returns the last element of the list.  If the list has no elements, returns nil.

 

rest()

Returns the "tail" of the list; that is, the rest of the list after removing the first element.  If the list has no elements, returns nil.  This function is almost the same as sublist(2), except that sublist() would return an empty list if given an empty list, whereas rest() returns nil in this case.

 

Identical to cdr().

 

intersectColl(coll)

Returns a new list consisting of the intersection of this list and coll; that is, a list consisting of the elements common to both this list and coll.  coll must also be either a vector or a list. 

 

subcollection(start, [len])

Identical to sublist(). Provided for polymorphism with Vector class objects.

 

toList([args])

Provided for polymorphism with Vector class object. Returns a list or sublist of this list. If arguments are provided the first argument should be the position in the list to begin the sublist. If a second argument is provided it should indicate the number of elements to include in the new list.

 

toVector([args])

Returns a vector of this list or its sublisting. If arguments are provided the first argument should be the position in the list to begin the sublist. If a second argument is provided it should indicate the number of elements to include in the new list.

Vector Methods

These methods extend the function of the Vector Class. Many of these methods have been added to allow for polymorphism with List class objects.

 

copyElmFrom(source, [args])

Copies values from a list or from another list or vector into this vector.  This function doesn't create a new vector, but simply modifies entries in the 'self' vector.  source is the source of the values; it must be either a vector or a list.  sourceStart is an index into source, and specifies the first element of source that is to be copied.  destStart is an index into the 'self' vector, and specifies the first element of the vector that is to be modified.  Count is the number of elements to modify.  The method copies elements from source into the 'self' vector, one at a time, until it reaches the last element of source, or has copied the number of elements specified by count.

 

createCopy([args])

Copies the elements of this vector to a new instance of Vector.

 

intersectColl(coll)

Returns a new vector consisting of the intersection of this vector and coll; that is, a vector consisting of the elements common to both this vector and coll. coll must also be either a vector or a list. See also List intersectColl().

 

firstElm()

Returns the first element of the vector.  If the vector has no elements, returns nil.

 

lastElm()

Returns the last element of the vector.  If the vector has no elements, returns nil.

 

rest()

Returns the "tail" of the vector; that is, the rest of the vector after removing the first element.  If the vector has no elements, returns nil.  This function is almost the same as sublist(2), except that subvector() would return an empty vector if given an empty vector, whereas rest() returns nil in this case.

 

subvector(start, [len])

Creates and returns a new vector consisting of a subvector of this vector starting at the element of this vector at index start, and continuing for the number of elements given by length, if present, or to the end of this vector if not.

 

subcollection(start, [len])

Identical to subvector. Provided for polymorphism with List class objects.

 

toVector([args])

Provided for polymorphism with List class objects. This method is equivalent to subvector and subcollection in that it will produce a new vector value. The method can take 2 optional arguments. The first indicates the position in which to start subvectoring, and the second indicates the number of elements from the starting position to add to the new vector.

 


LookupTable Methods

These methods extend the function of the LookupTable Class.

 

subset(func)

Creates and returns a new lookup table containing the elements of this lookup table for which the callback function func returns true (i.e., any value other than nil or the integer value 0).  For each element of the source lookup table, this method invokes the callback function, passing the key and value of the current element as the callback function's arguments.  If the callback returns nil or the integer value 0, the method omits the element from  the result; otherwise, the method includes the element in the result lookup table.

 

transpose(isomorph, [func])

Returns a new lookup table whose key-value relationships are the transposition of this lookup table. In other words, the keys of this lookup table become the values of the new lookup table, while the values of this lookup table become the keys of the new lookup table.

 

isomorph indicates whether the keys and values of this lookup table are to be considered isomorphic, having a one-to-one relationship: one and only one key for each value of this lookup table. If two keys in the table can return the same value, then we would lose one of the keys by simple transposition and should set isomorph to nil so that both keys in this table will be accumulated in a list value.

 

If func is provided then the transpose will be performed on the subset() of this lookuptable as derived from the function.

 

One common use for transpose() would be the recreation of a “reverse symbols table” which can be done simply with the following code:

 

revSymtab = t3GetGlobalSymbols().transpose(true);

 

 


Conditional and Case Functions

The functions in the tsp_cond.t module provide alternatives to the traditional TADS if-then and switch statements.

 

The Conditional Function

The Conditional is an alternate form of if-then statement that is composed of pairs of anonymous functions. Each pair consists of a predicate function and an action function. The form of the Conditional is as follows:

 

 

Cond( {: predicate-1 }, {: action-1 },

{: predicate-2 }, {: action-2 }, …);

 

Each predicate of a predicate/action pair in the Conditional is evaluated until one returns true (predicate() != nil && predicate() != 0) or the predicate/action list has been exhausted. Once a predicate evaluates to true (or non-zero) its associated action function is executed and no further predicates are evaluated.

 

The Conditional returns the value returned by the executed action function. If no predicate evaluates to true then the Conditional returns nil.

 

An example of the operating of the Conditional is the following:

 

local x = 15;

 

Cond({: x > 20}, {: tadsSay('x > 20') },

        {: x > 10}, {: tadsSay('x > 10') },

        {: x > 5}, {: tadsSay('x > 5') });

 

The Conditional will first evaluate the predicate of the first predicate/action pair: x > 20, and finding this to be false, will skip the associated action function.

 

The Conditional continues to the evaluation of the predicate of the next predicate/action pair: x > 10. Since this evaluates to true the Conditional executes the associated action function and displays ‘x > 10’.

 

No further predicates are evaluated, and the Conditional returns the return value of the executed action function, in this case nil.

 

The Case Function

The Case is an alternate form of switch statement that is composed of an anonymous function expression followed by pairs of anonymous condition/action functions. The form of the Case is as follows:

 

 

Case( {: expression },

{: condition-1 }, {: action-1 },

{: condition-2 }, {: action-2 }, …);

 

Each condition of a condition/action pair in the Case is evaluated and compared to the evaluated expression function until the comparison is true (expression() == condition-x()), or the condition/action list has been exhausted. Once a condition is found to equal the expression its associated action function is executed and no further conditions are evaluated.

 

The Case returns the value returned by the executed action function. If no evaluated condition equals that of the evaluated expression then the Case returns nil.

 

An example of the operating of the Case is the following:

 

local x = 15, y = 20, z = 30;

 

Case({: x },

        {: y - 10}, {: tadsSay('cond-1')},

        {: z - 10 }, {: tadsSay('cond-2')},

        {: z - y + 5 }, {: tadsSay('cond-3')},

        {: x}, {: tadsSay('catch-all')});

 

The Case will first compare the evaluated expression with the evaluation of the condition of the first condition/action pair: (x == y – 10), and finding this to be false, will skip the associated action function.

 

The Case continues to the comparison of the condition of the next condition/action pair: (x == z – 10) and so on until it finds a condition equal to the expression: (x == z – y + 5). Since this evaluates to true the Case executes the associated action function and displays ‘cond-3’.

 

No further conditions are evaluated, and the Case returns the return value of the executed action function, in this case nil.

 


Delegate Object

Creates a dynamic delegation object that an object can delegate work to. This object's state is set to that of the delegating object, so that the delegated object knows what the calling object knows.

 

This mechanism is useful in cases where TADS 3 language 'delegated' keyword is not appropriate. For example, suppose we have the following objects defined:

 

xObj: object

{

    attr1  = 'a'

    attr2  = 'b'

    attr3  = 'c'

 

    method1a() { delegated yObj.method1(); }

    method1b() { delegateObjMeth(yObj,&method1); }

 

    method2() { "hello xObj 2 <<attr1>>"; }

    method3() { "hello xObj 3 <<attr3>>"; }

}

 

yObj: object

{

    attr1  = 'd'

    attr2  = 'e'

    attr4  = 'f'

 

    method1() { return method2(); }

    method2() { "goodbye yObj 2 <<attr1>>"; }

    method4() { "goodbye yObj 3 <<attr3>>"; }

}

 

Suppose we want xObj to delegate its method1a() call to yObj. Using the TADS 3 'delegated' keyword the call chain would look like this:

 

xObj.method1a()       calls     yObj.method1()

yObj.method1()         calls     xObj.method2()

 

and this displays:

 

'TADS 3 DELEGATED : hello xObj 2 a'

 

Suppose we use the delegateTo() method of tsp_delegate_obj.t. The call chain from xObj.method1b() would look like this:

 

xObj.method1b()       calls     yObj.method1()

yObj.method1()         calls     yObj.method2()

 

and this displays:

 

'DELEGATE TO METH : goodbye yObj 2 a'

 

In this case the method calls for delegatedObjMeth() all remain within the yObj delegation, which has been set to the same state as the calling object. In addition the yObj delegation synchronizes the state of the calling object.

 

Three macros are provided for convenience:

 

delegateObj(delObj, args…) takes as arguments the object you wish to delegate to and the arguments to be passed to the object’s method. For this macro, the method is the same as that of the caller.

 

delegateObjMeth(delObj, method, args…) takes as arguments the object you wish to delegate to, the method you wish to delegate to, and the arguments to be passed to the object’s method.

 

delegateMeth(method, args…) takes as arguments the method you wish to delegate to, and the arguments to be passed to the object’s method and creates a delegation object from inheritedobj (see TSP Inheritance Order).

 

All of these macros will set the state of the caller to that of the delegated object’s properties that were changed as a result of the delegation.

 

 


Flags

Flags provide a mechanism for supporting bit flags and bitwise operations beyond the limitations of the normal TADS 3 32-bit infrastructure.

 

Working with Flags and Bit Flags

Flags provide a compact way of storing, evaluating, and manipulating object characteristics.

 

The relationship between bit number and bit value is as follows:

 

bit0 => 2^0 = 1

bit1 => 2^1 = 2

bit2 => 2^2 = 4

bit3 => 2^3 = 8

. . . For n bits:

 

The low order bit is indexed at 0

The high order bit is indexed at n - 1.

 

 

The usual approach to defining bit flags is to use #defines and assign integer value powers of 2:

 

#define           TAKEBIT                      1          // 20

#define           TRYTAKEBIT               2          // 21

#define           CONTBIT                     4          // 22

#define           DOORBIT                    8          // 23

#define           OPENBIT                     16        // 24

. . .

 

And so on until we reach the limitations of an integer value.

 

We could then model the state of an open door like this:

 

door: object

{

            state = (DOORBIT|OPENBIT)

}

 

We’ve assigned the state by combining two bit flags using the OR bitwise operator. We could then interrogate the door’s state to determine if it were open by using the AND bitwise operator, as follows:

 

If ((door.state & OPENBIT) != 0)

            do-something;

 

As we noted, up to 31 bit flags can be defined in this way before the maximum integer value is reached. It sounds like this ought to be more than enough for any definition. But when using flags to keep track of object characteristics we can quickly bump up against the 32-bit limit. For instance, ZIL employed at least 34 flags as documented in the ZIL.pdf.

 

The TSP Flags library extension provides the means for an author to work with bit flags beyond the 32-bit limitation.

 

BitFlag Class

The BitFlag class derives from the Flag class, specialized to allow ease of definition. In this implementation a bit flag is an instance of Flag that has 1 and only 1 bit turned “on”.

 

Creating Bit Flags

There are two ways to create instances of BitFlag. You can either statically define the flag, assigning it a bitNum value, like this:

 

TakeBit: BitFlag

            bitNum = 0

;

 

or you can create one dynamically by using the new operator and passing the class a bitNum value, like this:

 

new BitNum(3)

 

DefBitFlag Macro

Because static definitions of bit flags are so common and useful, the library extension provides a macro for compactly defining static bit flags. Using the macro, the equivalent of the bit flag #defines above would be:

 

TakeBit:          DefBitFlag(0);            // 20

TryTakeBit:     DefBitFlag(1);            // 21

ContBit:           DefBitFlag(2);            // 22

DoorBit:          DefBitFlag(3);            // 23

OpenBit:          DefBitFlag(4);            // 24

. . .

 

The DefBitFlag() macro defines the superclass of each bit flag, and indicates which bit is to be turned “on” in the bit flag. In the above examples the #define for TAKEBIT has a value of 1, while the DefBitFlag() macro for TakeBit passes ‘0’ as its argument.

 

In addition, the macro will throw a compile-time exception if the bitNum is greater than that allowed by the particular implementation of Flags (the default is 63).

 

 

Flag Class

The Flag class encapsulates a collection used to store bit values. This allows the Flag class to overcome the 32-bit limitation. Although the default implementation allows for only 64 flags, this limit is modifiable, and since the collection has an upper limit in the millions of bytes, for practical purposes there is no limitation to the number of bit flags an author can define.

 

 

Flag Validation

For the sake of efficiency, Flags doesn’t validate the ranges for bit numbers, bit strings, or element numbers in its computations unless you tell it to do so. The DefBitFlag macro will automatically throw a compile-time error if the bit number is greater than the maximum allowed by the implementation. But this is the only range-checking done by the implementation unless it is specifically told to do so.

 

If you want this further safeguard, you must specifically tell Flags to validate ranges for bit numbers and elements by defining  __FLAG_VALIDATE__ in your compile.

 

In the majority of cases this won’t be necessary, since most of the referencing by bit number and element number are internal to the extension’s computation methods. In most of the cases where you could reference an invalid bit number or element number value, the system will throw an exception error at the lower level of its implementation.

 

 

Creating Flags

To create an instance of Flag class you use the new operator, and pass its constructor either a single-quote string of the binary representation of its value or a series of flags. For instance, suppose we have the following bit flags defined:

 

TakeBit:          DefBitFlag(0);            // 20

TryTakeBit:     DefBitFlag(1);            // 21

ContBit:           DefBitFlag(2);            // 22

DoorBit:          DefBitFlag(3);            // 23

OpenBit:          DefBitFlag(4);            // 24

 

We could represent the state of an open door with the following flag:

 

state = new Flag(DoorBit, OpenBit);

 

Or we could create this flag using a “bit string” representation. If we use the “bit string” approach we don’t need to supply high-order zeros.

 

state = new Flag(‘11000’);

 

We can create a “bit string” representation for any existing flag simply by passing it the toBitString() message, indicating whether or not we want a space separating each byte:

 

str = new Flag(‘11000’).toBitString(true);

 

The length of the string will be determined by the Flag class’ collSize attribute, which is set to a default of 2. This will allow Flag to accommodate up to 64 bits of information (The maximum number of bits in an implementation can be determined by adding 1 to the Flag class’ maxBitNum attribute).

 

 

Converting Between Bit Strings and Flags

Creating flags from “bit strings” and converting flags to “bit strings” is a handy convenience, both in making a flag humanly readable, and in providing a useful shortcut in designating the bits of a flag.

 

There is no native representation of the series of bits that represent a flag. Instead the Flags library extension makes use of single-quoted strings consisting of binary values, separating bytes, if desired, by spaces.

 

So, for instance, this is a “bit string”:

 

‘111’

 

and so is this:

 

‘00000000000000000000000000000000 00000000000000000000000000000111’

 

The cvtBitString() Method

A flag can be created from a “bit string” representation, such as our new Flag(‘111’) example. The method that actually performs this conversion is the cvtBitString() method.

 

This method returns a new flag.

 

It ignores whitespace within the bit string, and counts only non-whitespace characters to determine the bit number to set. The method only sets those bits whose corresponding “bit string” values are ‘1’.

 

An example of the syntax is:

 

flag = Flag.cvtBitString(‘1110 0111’)

 

The toBitString() Method

This method converts the bit values of a flag into a “bit string” representation. The “bit string” consists of all the bits of the flag (represented by maxBitNum +1). The method takes one optional argument: elemSep, which if true will separate each element of bits (by default 32 bits) by a single whitespace character in the string.

 

For example, for the default (64-bit) implementation:

 

new Flag('111').toBitString(true);

 

will return a “bit string” that looks like this:

 

‘00000000000000000000000000000000 00000000000000000000000000000111’

 

The disp() Method

When this method is called it will convert the flag to a “bit string” value and display it. This method is intended for debugging purposes.

 

 

Setting and Clearing Flag Bits

Both setBits() and clearBits() apply their results to the flag receiving those messages and return self. Both methods can take multiple flag arguments, applying the results recursively to the flag receiving the message.

 

Because these methods return self, they can be used like the bitwise methods and we can chain messages, as in the following example:

 

new Flag('111').clearBits(new Flag('10')).disp();

 

The setBits() Method

This method sets the corresponding bits of the receiving flag “on” if they are “on” for the flag argument. No action is taken for those bits of the argument flag that are not turned “on”.

 

flag1.setBits(flag2)

 

is equivalent to

 

flag1 = flag1.or(flag2)

 

The clearBits() Method

This method sets the corresponding bits of the receiving flag “off” if they are “on” for the flag argument. No action is taken for those bits of the argument flag that are not turned “on”. This allows us to specify with bit flags, those bits of the receiving flag that we want to clear.

 

flag1.clearBits(flag2)

 

is equivalent to

 

flag1 = flag1.and(flag2.not())

 

One caveat: suppose we wanted to clear the DoorBit and OpenBit values for flag. Using flag.clearBits(DoorBit, OpenBit) wouldn’t work properly because it would clear all the bits, regardless of the flag’s bit values. To clear only the DoorBit and OpenBit we need to do the following: flag.clearBits(new Flag(DoorBit, OpenBit)).

 

 

 

 

Operating on Bits

Because flags and bit flags are objects, we can’t use bitwise operators on instances of the Flag class. We have to use the equivalent Flag methods.

 

Flag class provides methods that allow an author to perform bitwise and shift operations. Like BigNumber objects, the Flag object has specialized bitwise methods that replace the native bitwise operators.

 

These bitwise methods of Flag class don’t return integer values like bitwise operators do, they return Flag instances.

 

Bitwise Operator       Flag Method   Meaning

>>      rsh()   Shifts RIGHT by the number of bits specified; zero padded left.

<<      lsh()    Shifts LEFT by the number of bits specified; zero padded right.

!         not()   Bitwise NEGATION

&        and()  Bitwise AND

|        or()     Bitwise OR

^       xor()   Bitwise XOR

 

 

 

The bitwise methods of Flag class don’t return integer values like bitwise operators do, they return Flag instances.

 

Messages to the Flag class can be chained. Chained messages are executed from left to right.

 

 

For example:

 

new Flag(‘111’).and(bit1).or(bit3).disp()

 

is equivalent to the following:

 

flag = new Flag(‘111’);

flag = flag.and(bit1);

flag = flag.or(bit3);

flag.disp();

 

Using Bitwise Methods

The Flag bitwise methods not(), and(), or(), and xor() all take flag arguments. The and(), or(), and xor() methods can take multiple flag arguments, which are recursively applied to the results of the previous argument. For instance:

 

flag.and(DoorBit, OpenBit).lval()

 

would return nil in every case because it is equivalent to ((flag & DoorBit) & OpenBit). If you want to check if the flag has both DoorBit and OpenBit set you would code it as flag.and(new Flag(DoorBit, OpenBit)).lval().

 

The not() bitwise method takes no argument. It returns the complement of the flag. For instance,

 

new Flag(‘1010’).toBitString(true)

 

would return:

 

‘11111111111111111111111111111111

11111111 111111111111111111110101’.

 

Using Shift Methods

The shift methods return a flag that has its bits shifted either left or right the indicated distance. For instance

 

new Flag(‘1111’).lsh(4).toBitString(true)

 

will return a string value

 

‘00000000000000000000000000000000 00000000000000000000000011110000’

 

Both left and right shifts pad with zeros.

 

 

Evaluating Flags

Much of the time we want to evaluate the results of a bitwise operation to an integer or logical value. A common example is to interrogate a flag for a certain bit value:

 

if ((flag & TakeBit) != 0)

            do-something;

 

We can’t use bitwise operators on the Flag class, we have to use the equivalent method. But the bitwise methods in Flag class don’t return integer values, they return Flag instances.

 

ival([bitNum])            Returns 1 to indicate that the flag has at least one bit turned “on” otherwise returns 0. If a bitNum is passed then it evaluates the specified bit in the flag.

 

lval([bitNum])            Returns true to indicate that the flag has at least one bit turned “on” otherwise returns nil. If a bitNum is passed then it evaluates the specified bit in the flag.

 

The ival() Method

The ival() method examines the bits of an instance of Flag and returns an integer value (1/0) indicating whether or not at least one of the bits of the flag is “on”.

 

So, one way to code the equivalent interrogation of flag for TakeBit using the Flag ival method would be:

 

if (flag.and(TakeBit).ival() != 0)

            do-something;

 

 

The lval() Method

The lval() method examines the bits of an instance of Flag and returns a logical value (true/nil) indicating whether or not at least one of the bits of the flag is “on”.

 

So, one way to code the equivalent interrogation of flag for TakeBit using the Flag lval method would be:

 

If (flag.and(TakeBit).lval())

            do-something;

 

 

 

Comparing Flags

Sometimes we want to compare a flag, or the results of a bitwise method to another flag. A typical example is as follows:

 

if ((flag & TakeBit) == TakeBit)

            do-something;

 

As we can’t use bitwise operators on the Flag class, we have to use the equivalent method. But the bitwise methods in Flag class don’t return integer values, they return Flag instances.

 

The equals() Method

If you wanted to compare the result of a bitwise method to a flag, you could simply use the equals() method, like this:

 

if (flag.and(TakeBit).equals(TakeBit))

            do-something;

 

The equals() method returns a logical value (true/nil) to indicate whether the two flags have equal bit values.

 

The comp() Method

This method returns the following integer values:

 

-1       The bit values of the flag receiving the &comp message are less than the bit values of the flag in the argument

0        The bit values of the flag receiving the &comp message are equal to the bit values of the flag in the argument

1        The bit values of the flag receiving the &comp message are greater than the bit values of the flag in the argument

 

You would code the comparison similarly to the following:

 

if (flag.and(TakeBit).comp(TakeBit) == 0)

            do-something;

 

 

Counting Bits

You can count the number of bits in a Flag, or in an element of a flag, by using the bitCount() method. This method will return an integer value of the number of “on” bits for the Flag or the element in question.

 

For instance:

 

new Flag('101').countBits()

 

would return 2; while

 

new Flag(bit33, bit32, bit31).countBits(1)

 

would return 2.

 

 

 

 


Inheritance Order

An object’s inheritance order is a linear list of objects that comprise the traversal of an object’s inheritance hierarchy. The list represents the order in which an object’s properties are inherited.

 

The inheritance order of an object always begins with the object itself, and ends with the Object intrinsic class from which all objects derive.

Inheritance Structure List

The getInherStrucList() message can be sent to any Object class object.

 

object-0.getInerStrucList()

 

will return a list consisting of the following elements:

 

[

[object-0 [ directlyDefinedPropertyList ]],

[object-1 [ directlyDefinedPropertyList ]],

[object-2 [ directlyDefinedPropertyList ]],

…]

 

The method iterates over the entire inheritance order of the object, and is capable of producing a prodigious list, indeed. The following is an example of the partial list produced from me.getInherStrucList():

 

[

[me, [&location, &pendingCommand, &antecedentTable, &objRefTag]],

[Actor, [&name, &itNom, &theNamePossNoun, &itIsContraction, &conjugateRegularVerb, &itPossNoun, &pronounSelector, &verbEndingS, &itVerb, &itReflexive, &verbEndingEs, &theName, &thatObj, &verbWas, &thatNom, &travelerName, &aNameObj, &verbToBe, &verbEndingIes, &theNamePossAdj, &itPossAdj, &itIs, &theNameObj, &aName, &conjugationSelector, &verbToHave, &itObj, &objRefTag]],

. . .

]

 

The full list would continue for several pages.

 

The method takes a variable argument list. The valid signatures are:

 

getInherStrucList()

getInherStrucList(suppress)

getInherStrucList(func)

getInherStrucList(func, suppress)

 

If suppress is true then object / property list sub-elements are not added to the list when the property list is empty.

 

The callback function func should be of the form func(obj, prop) that returns either true or nil. If the function returns true the object / property sub-element is added to the list, otherwise it is not.

 


Inheritance Order List

The message getInherOrderList() returns a list of the objects comprising the inheritance order for an object.

 

me.getInherOrderList() produces a list consisting of some 12 objects.

 

me

Actor

 65b

Thing

 65f

VocabObject

 488

Schedulable

Traveler

594

TravelMessageHandler

ActorTopicDatabase

TadsObject

Object

 

It can be seen from this list that none of the objects occur more than once in the structure list and that the list begins with me and ends with Object. Even though the method iterates down and across the entire inheritance hierarchy, only unique objects are added to the list in the order that they are encountered in the traversal. The objects appearing as numbers are unnamed internals and indicate that the named objects proceeding in the list have been modified by the modify keyword.

 

The method takes a variable argument list. The valid signatures are:

 

getInherOrderList()

getInherOrderList(func)

 

The callback function func should be of the form func(obj, propList) that returns either true or nil. If the function returns true the object is added to the list, otherwise it is not.

Next Inheritance Order

The property getNextInherOrder() returns the next object in the inheritance order based on the arguments list passed.

 

The method can take 0, 1, or 2 arguments. If no argument list was passed then “self” is used as the starting position and the method returns the next object following the “self” object in the inheritance order. If 1 argument is passed then it is evaluated to determine if the argument is an integer or an object. If it is an integer N, then the method will return the N+1 object in the inheritance order. If the argument is an object then the inheritance order is scanned for that object and the next object in the list is returned.

 

If the method is passed a second argument then this argument should be either true, or nil, to indicate whether the method should either throw a runtime error or simply return nil when no next object in the inheritance order was found.

 

The inheritedobj Macro

This macro is equivalent to calling getNextInherOrder(definingobj, true), which means that it will return the next object in the inheritance order of the defining object for the method in which it is coded.

 

An example:

 

obj1: object

{

    myPtr = 'obj1'

    myMethodC()

    {

        "<<myPtr>> ";

    }

}

 

obj2: obj1, me

{

    myPtr = 'obj2'

    myMethod1()

    {

        inheritedobj.myMethodC();

    }

    myMethod2()

    {

        inherited.myMethodC();

    }

    myMethod3()

    {

        delegated obj1.myMethodC();

    }

}

 

The inheritedobj macro returns, by calling obj2.myMethod1() the value ‘obj1’; while both the inherited and delegated keywords return, by calling obj2.myMethod2() and obj2.myMethod3() respectively, the value ‘obj2’.

 

Inheritance Order Property List

The getInherPropList() message take the same approach as the getInherSuperclassList() message, producing a list of the properties that the object defines or inherits throughout the inheritance hierarchy. The list contains only one occurrence of each property, when the property is encountered in the inheritance traversal.

 

The method takes a variable argument list. The valid signatures are:

 

getInherPropList()

getInherPropList(func)

 

The callback function func should be of the form func(obj, prop) that returns either true or nil. If the function returns true the property is added to the list, otherwise it is not.

 

Inheritance Order State Structure List

State is reflected by an object’s attributes, behavior by an object’s methods. Together attributes and methods provide the state and behavior that make up an object’s representation of whatever it is modeling. The message getInherStateStrucList() returns a structure list composed only of the objects / properties that reflect an object’s state.

 

For instance, the following partial list is returned from me.getInherStateStrucList():

 

[

[me, [&location, &pendingCommand, &antecedentTable, &objRefTag]],

[Actor, []],

[tads#45a ( 4b0), [&travelerPreCond, &isListedAboardVehicle, &isLikelyCommandTarget, &nextRunTime, &preCondDobjAskFor, &preCondDobjTalkTo, &bulkCapacity, &maxSingleBulk, &scheduleOrder, &isListedInInventory, &preCondDobjKiss, &takeFromNotInMessage, &actorNotifyList, &smelllikeSenses, &sightlikeSenses, &inventoryLister, &nextHoldingIndex, &pcReferralPerson, &scopeSenses, &holdingDescInventoryLister, &isActor, &issueCommandsSynchronously, &hearinglikeSenses, &followables_, &posture, &wearingLister, &holdingDescWearingLister, &weightCapacity, &communicationSenses]]

            . . .

]

 

The list returns only the attributes for each trait of the object that would be reflected by self.(prop). So if more than one trait defines an attribute only the one for which self.(prop) would reflect the value is chosen. This is called the object’s defined property and is discussed further below.

 

Where an object in the structure list does define any properties, in this instance attributes, the property list is empty. Such is the case with Actor, which is a modification of Actor, and deriving from tads#45a ( 4b0).

 

The method takes a variable argument list. The valid signatures are:

 

getInherStateStrucList()

getInherStateStrucList(suppress)

getInherStateStrucList(func)

getInherStateStrucList(func, suppress)

 

If suppress is true then object / propList sub-elements are not added to the list when the propList is empty.

 

The callback function func should be of the form func(obj, prop) that returns either true or nil. If the function returns true the object / property sub-element is added to the list, otherwise it is not.

 

Inheritance Order Defined Structure List

An object’s defined property is the key to returning an object’s defined structure. Suppose we have the following object definitions:

 

myObject: Thing

{

            myAttribute = ‘hello’

}

 

myChildObject: myObject

{

            myAttribute = ‘goodbye’

}

 

anotherObject: myChildObject

{

}

 

Now what is returned by the message anotherObject.myAttribute? Traversing the inheritance order hierarchy we come to myAttribute first defined in myChildObject. Thus myChildObject is the defining trait for the property myAttribute for object anotherObject.

 

Thus getInherDefStrucList() would return a list that we partially display below:

 

[

[anotherObject, []],

[myChildObject, [&myAttribute]],

[myObject, []],

. . .

]

 

This list agrees with our intuitive assessment.

 

Retrieving an object’s defined structure is so useful that TSP provides the message getInherDefPropList().  This message returns a list of those properties that are meet the self.(prop) test we mentioned earlier.

 

The method takes a variable argument list. The valid signatures are:

 

getInherDefStrucList()

getInherDefStrucList(suppress)

getInherDefStrucList(func)

getInherDefStrucList(func, suppress)

 

If suppress is true then object / propList sub-elements are not added to the list when the propList is empty.

 

The callback function func should be of the form func(obj, prop) that returns either true or nil. If the function returns true the object / property sub-element is added to the list, otherwise it is not.

 

The createClone() Alternatives

TSP inheritance order provides three methods that create clones of an object.

 

createTspOddBsmClone()                    creates a clone having only the states and behaviors directly defined by the object being cloned and will inherit solely from the TspSimObject class. This “flat” clone provides a convenient way of “slicing off” the front end behavior-states of an object.

 

createTspMimClone()       creates a clone composed of a set of object directly-defined behavior-state clones representing the object’s inheritance order.

 

createTspSimClone()        creates a clone composed of object directly-defined behavior-state clones representing the object’s inheritance order and restructured so that each clone in the inheritance order is derived from the succeeding one.

 

The TspSimObjectCvtr class

This class restructures the object that inherits it during pre-initialization into a TSP single-inheritance model clone of its original definition. To define a statically-defined TspSimObject you simply add the class to an object’s superclass definition. For example:

 

SimActor: TspSimObjectCvtr, Actor;

 

This definition will create a tsp single-inheritance model object of Actor during pre-initialization that can be referenced by the symbolic name SimActor. In addition, the object’s new inheritance order will not include the TspSimObjectCvtr class, but the object will inherit from TspSimObject.

 

Note that a tsp single-inheritance model clone restructures the inheritance model of an object, and yet keeps intact the original inheritance order of the classes the object’s clone is created from.

 

For instance, a class defined as:

 

A: object;

B: object;

 

simObj: TspSimObjectCvtr, A, B;

 

Will create an object simObj that is comprised of object directly-defined behavior-state clones of A and B. Although both of the OddBsm clones will have the states and behaviors of A and B respectively, they will be solely derived from TspSimObject. When the object is restructured simObj will inherit from the object directly-defined behavior-state clone of A, which will inherit from the object directly-defined behavior-state clone of B, which in turn will inherit from TspSimObject.

 

It is important to note that a TspSimObject, while inheriting the states and behaviors of its original superclass list, will not be ofKind() for any of them. It can only be considered as in instance of TspSimObject.

 

To determine the “nature” of the object you can inquire using the oddBsmOfKind() method and getOddBsmPtrList()

 

oddBsmOfKind(obj)           return true if the object inherits from an object directl-defined behavior-state clone that was cloned from obj; otherwise the method returns nil.

 

getOddBsmPtrList()          returns a list of all of the object’s oddBsmPtr objects. These are the objects that formed the inheritance order of the original object that this object is a clone of.


Method Overloading

Routes to a method built from the targetprop and argument count. This lets the author define a simple form of "method overloading" on a given method. For instance, just #include method_overload.h in your source, and include method_overload.t in the compilation.

 

Then you can define an object like this:

 

myObject: object

{

MethodOverload(construct)

{

args0() {...}

args1(arg1) {...}

args2(arg1, arg2) {...}

}

 

MethodOverload(sum)

{

args2(arg1, arg2) {...}

args3(arg1, arg2, arg3) {...}

}

}

 

This lets you code the methods without having to code for variable argument lists and the usual switch statement required to handle each situation.

 

CAVEATS

Note that no type determination is involved, overloading is done based solely on the *number* of arguments, not their type.

 

If a method hasn't been defined for the corresponding number of arguments, then a runtime error is returned indicating "wrong number of arguments".

 


Module Registry

This TSP module defines PlaceHolder, module registry and module dependency classes. Each of these mechanisms attempts to handle the presence or absence of modules, objects, or properties during program compilation.

 

The PlaceHolder class

This class inherits the superclasses of indicated by its forObject and defaultObject properties only if those objects are present at the time of compilation. Otherwise the object retains its PlaceHolder superclass. For instance, we can define a ModuleID object as follows:

 

PlaceHolder

{

    forObject = ModuleID

 

    name = 'TADS 3 Services Pack'

    byline = 'by Kevin L.\ Forchione'

    htmlByline = 'by <a href="mailto:kevin@lysseus.com">Kevin L.\ Forchione</a>'

    version = '1.0'

 

    /*

     *   We use a listing order of 60 so that, if all of the other credits

     *   use the defaults, we appear after the TADS 3 Library's own credits

     *   (conventionally at listing order 50) and before any other extension

     *   credits (which inherit the default order 100), but so that

     *   there's room for extensions that want to appear before us, or

     *   after us but before any default-ordered extensions. 

     */

    listingOrder = 60

}

 

If the class’ forObject property value “ModuleID” is present as an object definition during compilation then the superclass of this object is set to that of ModuleID and this object will then behave just like any other ModuleID. If the ModuleID class is not present during compilation then the object retains its PlaceHolder superclass, which allows the object to compile as a dummy object for documentation purposes only.

 

The RegisterModule and RequiresModule() Macros

These macros work together to ensure that a required module is present during compilation for debugging. This information is only available when we are compiling for debugging.

 

First, each module that might be required for a compilation should define the RegisterModule macro. The module can occur anywhere after the #include of the tsp_mod_registry header. For instance:

 

#include “tsp_mod_registry.h”

 

RegisterModule

This macro takes no parameters. During compilation for debugging the name of the module will be registered with the ModuleRegistry object. If we are not compiling for debugging a warning message will be displayed on the Build Window to indicate that the TSP Module Registry information is not available at this time.

 

If a module requires that another module be present during compilation for debugging, then the RequiresModule() macro should be defined in the module after the #include of the tsp_mod_registry header. For instance:

 

#include “tsp_mod_registry.h”

 

RegisterModule

 

RequiresModule(‘my_lib_extension.t’)

 

The RequiresModule() macro takes one argument, the single-quoted string value of the name of the required module (If more modules are required, then multiple RequiresModule() macro statements should be used.)

 

During compilation for debugging, this code will ensure that this module registers itself with the ModuleRegistry and will then check to see if my_lib_extension.t has been registered with the ModuleRegistry. If not then a runtime exception is thrown indicating that the required module is not present. If compilation is not for debugging, then no enforcement is made.

 

Conditions for Using Module Registration

  1. Enforcement of module registration occurs only during compilation for debugging.
  2. If we are not compiling for debugging then a warning message will be displayed in the Build Window indicating that registry information is not available at this time. Enforcement of module registration is bypassed.
  3. Each module that might be required must define a RegisterModule macro and #include the tsp_mod_registry.h header file.
  4. Each module that requires another module during compilation must define a RequiresModule() macro with the full name of the required module in a single-quoted string.

 

The ModuleDependency class

Each instance of this class stores a single dependency in a 'global' object's property pointer if and only if that dependency is found as a symbol in the global symbol table.

 

For instance:

 

ModuleDependency

{

globalStr   = 'myGlobal'

propStr     = 'myDependencyPtr'

dependStr   = 'myDependency'

}

 

Will store the dependency myDependency in myGlobal's myDependencyPrt property.

 

This object definition is equivalent having your program execute the following code:

 

myGlobal.myDependencyPtr = myDependency;

 

Each of the 3 properties, if defined, should be defined as a single-quoted string value. If globalStr is omitted then "self" will be assumed as the global object. If propStr is omitted then it will be created by appending 'Ptr' to the depndStr value. Thus the same results could be achieved for as the above, using the following definition:

 

ModuleDependency

{

globalStr   = 'myGlobal'

dependStr   = 'myDependency'

}

 

If assertDependency is set to true (the default) then a run-time exception will be thrown during preinit if the dependency wasn't found in the global symbols table.

 

 


Output

The TSP Output module provides specialized output support.

 

tSay(ln, tb, [vals])

The ln argument indicates the number of lines to skip (0 – N). The tb argument indicates how many tabs (“\t”) to tab over. Finally the vals variable list are the values to be displayed.

 


Prototype Methods

The TADS 3 object model is not class-based. By this we mean that in a traditional class-based model classes are used to provide the blueprints from which instances are derived that do the actual work of the program. Classes do not generally receive messages except to create new objects. The purpose of the class is to provide the blueprint for a new object.

 

The Object Model

TADS adopts a distributed inheritance scheme. This means that an objects definition consists only of those properties that it directly defines. The other properties that constitute the state and behavior of the object, and which are inherited from other objects, are not part of the object definition, but are determined through a traversal of the object’s inheritance hierarchy.

 

To explore this further, let’s examine the following representation of an object’s definition:

 

<objectName>: classList

{

            <directlyDefinedPropertyList>

}

 

The <> symbols indicate that these elements are optional. Only the class list is required in an object definition.

 

Traits

TADS refers to the objects that other objects derive from as superclasses. But inheritance isn’t limited to classes. An object can inherit from any TadsObject class object or class. TADS 3 provides both the new operator and the createInstance() message for dynamically creating an instance of an object. TSP therefore adopts the term trait, taking on a more prototype-based perspective, in examining the TADS object model.

 

hasTrait(trait)

This is method is equivalent to ofKindOrUU() except that it doesn't rely on the ofKind() method definition. Instead it uses the object's inheritance order in its determination.

 

Object Derivations

The TADS 3 language provides the ofKind() message to determine if an object either inherits from a trait or is the object passed as the message argument. For instance, the statement:

 

Thing.ofKind(Thing)

 

returns true. This is in contrast with the TADS 2 language’s isclass() function, which would have returned nil in the case of comparing the TADS 2 thing class to itself. The TADS 3 ofKind() message is very useful in that it more fully embraces the prototypal perspective of the object model.

 

The new Operator vs. createInstance()

From a technical standpoint there is no difference between using the new operator as opposed to the createInstance() message, except that the latter

 

“…performs a recursive VM invocation on the constructor whereas 'new' invokes the constructor directly, but that's an internal detail that should be of no practical interest whatsoever to the code (the only way the code can tell the difference is to look at the stack traceback available through reflection).” (MJR).

 

While new can only be used with objects that have been designated as classes, there is no technical difference in using createInstance() on an object that has been designated as a class and one that has not.

 

Traversing the Inheritance Hierarchy

Inheritance in TADS 3 moves down the inheritance tree of a trait, and then across to the next trait in the class list, from left to right, iterating down through each inherited trait in this fashion until we have either found the property being evaluated or have reached the end of the hierarchy.

 

 

 

 

 

 

 

 

 

 

Complex as this process seems, it is an automatic one, and the author need not trouble himself with its functions, but merely understand that inheritance is fully distributed along the inheritance hierarchy of the object, and only terminates when we encounter the property being invoked, or reach the end of the inheritance hierarchy.

 

PropNotDefined

If the property is not encountered by the end of the inheritance traversal, then the VM may send obj1 the message propNotDefined(), if the object defines this method. propNotDefined() is the last final chance that an object gets to handle a message.

 

Intriguing Aspects of TADS Object Model

Changes to a Trait’s State Reflected in Children

Children in TADS 3 have their own peculiarities. Any changes of state made to an object’s traits during run-time are immediately reflected in the inheritance hierarchy of their children.

 

To demonstrate this, we can observe what happens when we define a simple object deriving from Thing class:

 

myObject: Thing;

 

If now we were to change the value of an attribute of Thing class, such as:

 

Thing.isHim = true;

 

We then find that every child of Thing inherits this change in attribute.

 

Modifying Attributes at Runtime

Because TADS inheritance model is a distributed one, changing an object’s state automatically adds the changed attribute to the object’s directly-defined property list. Further modification merely changes the value assigned to the attribute.

 

If we define myObject as follows:

 

myObject: Thing;

 

And at some point during run-time we change the state of the object, say by setting &isHim to true:

 

myObject.isHim = true;

 

Then the internal object definition for myObject changes to:

 

myObject: Thing

{

            isHim = true

}

 

Once an attribute has been added to an object’s directly-defined property list it cannot be removed. For instance, setting the &isHim attribute to nil does not remove it from myObject’s directly-defined property list, but merely changes the property’s assigned value to nil.

 

myObject.isHim = nil;

 

We now have:

 

myObject: Thing

{

            isHim = nil

}

 

Adding Attributes at Runtime

By the same token, it’s perfectly feasible to assign an attribute to an object, even when the attribute does not appear in the object’s inheritance hierarchy. The new attribute is added to the object’s directly-defined property list in a fashion identical to that of modification cited above.

 

 

Types of Objects

Classes

In TADS a class is an object that has been defined using the keyword class. This object is like any other symbolically-referenced object, except that the intrinsic method isClass() it inherits from Object return true. Objects not defined with the class keyword will return nil when sent the isClass() message.

 

The chief impact in distinguishing classes from other objects is that these objects are excluded from the parsing process.

 

 

Ancestors

 

 

Parents

The TADS 3 getSuperclassList() message is defined for all objects deriving from Object. It provides a list of those traits directly defined in the object definition. For instance, the following object definition:

 

            myObject: Fixed, Surface;

 

produces the superclass list [Fixed, Surface]. TSP duplicates and extends this functionality by providing the getParentList() message. We can also determine if an object is a parent of another object by use of the isParentOf() method.

 

Descendants

 

 

Children

In a prototypal perspective, derivations of an object are called children. In TADS 3 these children are created using either the new operator for classes, or the messages createInstance()or createClone() for objects. We can retrieve a list of an object’s children by using the getChildList(). We can also interrogate an object to determine if it is a child of another object by using the isChildOf() method.

 

 

 

 

getAncestorList([args])

Returns a list of all the unique inheritance ancestors for this object.

 

isDescendantOf(obj)

Returns true if this object is an ancestor of obj; otherwise returns nil.

isDescendantOf(obj)

TSP does provide a mechanism similar to the TADS 2 isclass() function. The message isDescendantOf(obj) can be sent to any Object class object. It will return true if the object derives from the object, but nil if the object == obj or does not derive from it.

 

getDescendantList([args])

This message is available for any Object class object and returns a list of all descendants of this object. The message takes a variable length list as its sole argument. If no argument is passed, then object classes and instances are returned in the list. The arguments should be the same flags passed to the firstObj() / nextObj() mechanism, which the method employs in order to build the list.

 

If no objects are derived from the object then the message returns an empty list.

 

getChildList([args])

Like the proceeding message, this one returns a list of objects derived from the object, but only if the lineage is immediate. An object is considered a child of another object if its getSuperclassList() contains the other object.

 

Suppose for instance, that we define the following relationships:

 

myObject: Thing;

 

myChildObject: myObject;

 

Only myObject is said to be a child of Thing class, as it will list Thing as an element of its superclass list. myChildObject, while a descendant of Thing, is not a child , as it will list only myObject in its superclass list.

 

The distinction provided by child is that it provides a limited list of those objects whose states and behaviors are most closely related to, being immediately derived from, the object under examination. We can, for instance, acquire a list of all children of BasicLocation in the library.

 

  • class NestedRoom
  • class tads#433 ( 481 *Room*)

 

 

We can then draw the relationships between BasicLocation, NestedRoom, and tads#433 (an unnamed internal object represented by ‘ 481’ in the global symbol table). We could further trace the chain of derivation from NestedRoom:

 

  • class HighNestedRoom
  • class tads#377 ( 3ac *Vehicle*)
  • class tads#454 ( 4ab *BasicChair*)

 

 

From this analysis we can see that HighNestedRoom is derived from NestedRoom, which in turn is derived from BasicLocation.

 

isDirectLineageOfKind(trait)

This message returns true if the object is ofKind(trait) and the lineage isn’t obscured by intervening multiple inheritance structures that might radically affect the state or behavior of the object.

 

Suppose we define the following object relationships:

 

myObject: Thing;

myChildObject: myObject;

myOtherObject: Fixed, Thing;

 

And we ask the question, which of these objects derive from Thing, and only from Thing, or is the Thing class itself? The answer would be: Thing and myObject. But myChildObject derives solely from myObject, and therefore is closely related to Thing, more so than myOtherObject, which derives from both Fixed and Thing.

 

It’s a question of object genealogy of a sort, and myChildObject.isDirectLineageOfKind(Thing) will return true; while myOtherObject.isDirectLineageOfKind(Thing) will return nil. A nil response indicates that the object being examined either does not derive from the trait, or does not derive solely from the trait.

 

First Ancestors

Ancestors play an important part in the naming of an object in TSP when an object doesn’t possess a symbolic object name. These objects are either dynamically-created or anonymously-defined, and displaying their object names using only object reference tags is too cryptic, so TSP incorporates ancestor information into the name of the object.

 

TSP provides the message getFirstAncestorList() for Object class objects and isfirstAncestorOf(obj) to indicate whether this object is a first ancestor of obj.

 

First Symbolic Ancestors

But an ancestor may have been dynamically-created or anonymously-defined, which means that we would only compound the cryptic nature of naming the object using object reference tags. So TSP works up the inheritance tree to find an ancestor that exists in the global symbol table. These objects were either statically-defined in library or author code, or dynamically-generated unnamed internal objects.

 

TSP provides the message getFirstSymAncestorList() for Object class objects and isFirstSymAncestorOf(obj) to indicate if this object is a first symbolic ancestor of obj.

 

First Named Ancestors

For dynamically-created or anonymously-defined objects TSP uses the first named ancestors to represent the object, in conjunction with an object reference tag. First named ancestors are the first ancestors encountered in traversing the inheritance hierarchy, where the trait is not dynamically-created, anonymously-defined, or an unnamed internal.

 

TSP provides the message getFirstNamedAncestorList() for Object class objects and isFirstNamedAncestorOf(obj) to indicate if this object is a first named ancestor of obj.

 

First Descendants

TSP provides the message getFirstDescendantList() for Object class objects. Without passing an argument the message returns the same information as getChildList(), but provides a means of iterating down the lineage of parent / children for an object.

 

First Named Descendants

Since unnamed internals are the result of modifications of object definitions, it is difficult to tell what named object the unnamed internal’s code once referred to. For dynamically-created or anonymously-defined objects an empty list is returned. For all other objects this is the same as getChildList().

 

TSP provides the message getFirstNamedDescendantList() for Object class objects.

 

The ofKindOrUU(obj) Method

This method is similar to the ofKind() intrinsic method in that it returns true if the this object is ofKind(obj). Otherwise it checks to see if obj is one of this object’s modifications. If so, then the method returns true. Otherwise the method returns nil.

 

 

 

 


PseudoGlobals

This TSP module provides a mechanism for creating and accessing “global variables”. To Define a global we use the DefG() macro:

 

DefG(maxScore, 100);

DefG(inventoryWide);

 

The first argument is the “global” to be defined, the second argument is its initial value. If no initial value is provided the global is assigned a value of true.

 

Once a global has been defined, its value can be retrieved using the GetG() macro. For instance:

 

GetG(maxScore);

 

Would return 100. To change the value use the SetG() macro:

 

SetG(maxScore, 75);

 

 

 

 

 

 


Regular Expression Token Matching

This file defines the rexTokMatch(), rexTokSearch(), and rexTokGroup() functions. These functilons do for a token list what rexMatch(), rexSearch(), and rexGroup() do for strings.

 

For instance, for a token list consisting of the following:

 

'the' 'red' 'ball' '.'

 

ret = rexTokSearch('(ball).') returns [TokenBoundaryMatch, [3,2,['ball','.']]

 

rexTokGroup(1) returns [TokenBoundaryMatch, [3,1,['ball']]

 

The rexTokXXXX functions come with a few caveats.

 

The first is that the string built from the token list is constructed from converted tokenizer token values, not original values.

 

The second caveat is that the default construction of the search string separates each token value by a single whitespace, except for tokPunct token values, which are appended to the previous token value.

 

The third is that it is possible to have a partial match on  a token value at either end of the token list used to build the search string. For instance, a search string of 'in' would only partially match the token value of 'into'. The  first element of each function's return value indicates  what kind of match has occurred.

 


Symbols

In the TSP Symbol module, the symbol table that is retained and stored during the pre-initialization stage of program compilation. The symbol table allows a program to access the global symbols that were defined during compilation and makes it possible to translate strings into object references, properties, and function references; as well as to translate these references into strings.

 

The Symbols Class

The Symbols object combines two LookupTables. One table contains the global symbols table produced by the compiler. This table associates single-quoted string keys with symbolic values. The other table reverses the associations to symbolic keys and single-quoted string values.

 

The methods provided by the Symbols object encapsulate and hide the two tables, so that the user need not be aware that they are dealing with two tables. The datatype of the argument is immaterial; if the key is a single-quoted string then the table keyed by single-quoted strings is searched. If not then the other table is searched.

 

Retrieving the Symbol for a Value

The Symbols object provides the method getSymbol(val) that returns the symbol associated with the key val found in the global symbols table. If the argument is not a key in the global symbol table then the method returns nil.

 

Retrieving the Single-quoted String for a Value

The Symbols object provides the method getSString(val) that returns the single-quoted string associated with the key val found in the global symbols table. If the argument is not a key in the global symbol table then the method returns an empty string.

 

Checking for the Presence of a Key in the Global Symbols Table

The Symbols object provides the following methods for determining if the appropriate key is present in the global symbols table:

 

isKeyPresent(key): this method first determines if the dataType() of key is TypeSString. If so then the method passes control to isStrKeyPresent(key); otherwise it passes control to isSymKeyPresent(key).

 

isStrKeyPresent(key): this method returns true if key is present in the LookupTable element of the global symbols table that associates strings to symbols. Otherwise it returns nil.

 

isSymKeyPresent(key): this method returns true if key is present in the LookupTable element of the global symbols table that associates symbols to strings. Otherwise it returns nil.

 

Objects without Symbolic References

The global symbol table is very useful for translating between symbolic references and string representations. Any object that has a symbolic reference, commonly called an object name, will have a value in the global symbol table. Objects that have been dynamically-created or anonymously-defined, however, will not have a value in the global symbol table, and will return an empty string for getSString() and nil for getSymbol() messages. TSP takes advantage of this fact by providing the isDynamOrAnon() message.

 

isDynamOrAnon() message will return true if the object is either dynamically-created or anonymously-defined. If the object has a symbolic reference it is neither, and the method will return nil.

 

An object definition such as the following would return nil for isDynamOrAnon(),

 

global: object;

 

because global would appear in the global symbol table and can be programmatically referenced by its object name.

 

An object dynamically-created from global using the createInstance() message would return true for isDynamOrAnon() and would need to be referenced through an object property or local variable.

 

Objects defined anonymously, such as the loaf of bread from the TADS 3 sample game:

 

++ Food 'fresh golden-brown brown loaf/bread/crust' 'loaf of bread'

    "It's a fresh loaf with a golden-brown crust. "

 

    /*

     *   we want to provide a special message when we eat the bread, so

     *   override the direct object action handler for the Eat action;

     *   inherit the default handling, but also display our special

     *   message, which will automatically override the default message

     *   that the base class produces

     */

    dobjFor(Eat)

    {

        action()

        {

            /* inherit the default handling */

            inherited();

 

            /* show our special description */

            "You tear off a piece and eat it; it's delicious.  You tear off

            a little more, then a little more, and before long the whole loaf

            is gone. ";

        }

    }

;

 

 would return true for isDynamOrAnon(). Programmatically, and for all practical purposes, anonymous objects are indistinguishable from dynamically-created ones. Anonymously-defined objects persist throughout game play, while dynamically-created ones are subject to garbage collection once they become de-referenced, but otherwise there is no programmatically significant difference between them.

 

Unnamed Internals

A close examination of the global symbol table reveals that it harbors some strange denizens. These symbolic references appear as hexadecimal values in the symbol table. They are symbolic references that aren’t defined in either the library or author’s code, instead they are created during compilation and are the result of modifications to object definitions resulting from the modify keyword.

 

When an author modifies an object definition using the modify keyword, the compiler automatically generates a hexadecimal symbolic reference (object name) for the definition occurring previously in the compilation process. A new object is created, deriving from the object being modified, and the new object directly defines the code defined by the modify keyword. This newly generated object is then given the symbolic reference (object name) of the object that is the operand of the modify keyword.

 

To better understand the process. Assume we code the following definitions in our program:

 

            global: object

            {

                        myAttribute1 = true

            }

 

            modify global

            {

                        myAttribute1 = nil

                        myAttribute2 = true

            }

 

Now when we compile the program, as the compiler encounters the modify keyword it generates a new hexadecimal symbolic reference for the previous definition of global, and places this reference into the global symbol table:

 

            4fe: object

            {

                        myAttribute1 = true

            }

 

Next it creates an object deriving from our re-referenced object, 4fe, and gives this object the symbolic reference (object name) that is the operand of the modify keyword:

 

            global: 4fe

            {

                        myAttribute1 = nil

                        myAttribute2 = true

            }

 

As the compiler proceeds through the code sequentially it follows the same process for each modification that it encounters. Each additional modification to global will produce a new unnamed internal, and the code defined by the modify keyword will be used to create an object derived from this unnamed internal, and given the symbolic reference of the modify keyword.

 

Although these compile-time generated unnamed internals can only be referenced programmatically through object properties or local variables, they are not subject to garbage collection and are similar to anonymously-defined objects.

 

The TSP Symbols object can be used to determine whether an object is an unnamed internal, based on whether the object has a hexadecimal symbolic reference. Object class objects can receive the message isUnnamedInternal(), which will return true if the object is an unnamed internally-generated object, or nil if the object is not.

 

If x is a local variable referencing an object, in the following statement

 

            val = x.isUnnamedInterna();

 

the variable val will either be assigned true or nil.

 

 

 

 


Tokenizer Token Synchronization

The TSP TokSync module is for library extensions and author games that need to add a new token to the command tokenizer. Simple create a TokenRuleObject, assign it an identifying name, the token rule you want to add to the command tokenizer, and a list of single-quoted token strings that the token rule should match.

 

The tokSynPreinit will search for all TokenRuleObjects and add them to the command tokenizer rules and checks to see that the new token rules are then valid when compared to their token string lists.

 

For example. If you wanted to add a time token to the library:

 

timeSysTokenRuleObject: TokenRuleObject

{

tokRule_    = ['token-name',

RexPattern('(<Digit>?<Digit>):(<Digit><Digit>)(<NoCase><space

(am|pm|a.m.|p.m.|a|p))?'),

tokTime, nil, nil]

tokStrList_ = ['5:00', '17:00', '3:00 pm', '3:00 p.m', '12:00 a.m.']

}

 

If any of the token strings are not matched by the token rule then tokSyncPreinit throws a RuntimeError.

 


Vector Methods

The methods in this module extend the functionality of Vector class objects.

 

copyElmFrom(source, [args])

Copies values from a list or from another list or vector into this vector.  This function doesn't create a new vector, but simply modifies entries in the 'self' vector.  source is the source of the values; it must be either a vector or a list.  sourceStart is an index into source, and specifies the first element of source that is to be copied.  destStart is an index into the 'self' vector, and specifies the first element of the vector that is to be modified.  Count is the number of elements to modify.  The method copies elements from source into the 'self' vector, one at a time, until it reaches the last element of source, or has copied the number of elements specified by count.

 

createCopy([args])

Copies the elements of this vector to a new instance of Vector.

 

sublist(start, [len])

Creates and returns a new vector consisting of a sublist of this vector starting at the element of this vector at index start, and continuing for the number of elements given by length, if present, or to the end of this vector if not.

 

intersect(vec2)

Returns a new vector consisting of the intersection of this vector and vec2; that is, a vector consisting of the elements common to both this vector and vec2. vec2 must also be either a vector or a list.  If the two vectors have no elements in common, the result is an empty vector.  If an element of the shorter vector (or, if the vectors are of equal length, this vector) appears more than once in the shorter vector, and that element value also appears in the longer vector, then the element will be in the result vector the same number of times that it is in the shorter vector.  An element repeated in the longer vector will not be repeated in the result vector.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

This file is part of the TADS 3 Services Pack

Copyright © 2005 Kevin Forchione. All rights reserved.