TADS 3 Services Pack
User’s Guide
Version 1.0.2
Contents
Conditional
and Case Functions
Converting
Between Bit Strings and Flags
Setting
and Clearing Flag Bits
Inheritance
Order Property List
Inheritance
Order State Structure List
Inheritance
Order Defined Structure List
The
createClone() Alternatives
The
RegisterModule and RequiresModule() Macros
Conditions
for Using Module Registration
The
new Operator vs. createInstance()
Traversing
the Inheritance Hierarchy
Intriguing
Aspects of TADS Object Model
Regular
Expression Token Matching
Retrieving
the Symbol for a Value
Retrieving
the Single-quoted String for a Value
Checking
for the Presence of a Key in the Global Symbols Table
Objects
without Symbolic References
Tokenizer
Token Synchronization
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.
The
methods in this module extend the functionality of Collection class objects.
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.
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.
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);
The
functions in the tsp_cond.t module provide alternatives to the traditional TADS
if-then and switch statements.
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
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.
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
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.
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”.
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)
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).
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.
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.
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).
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’
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’)
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’
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.
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();
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)
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)).
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();
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’.
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.
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 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 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;
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.
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.
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;
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.
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.
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.
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.
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.
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’.
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.
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.
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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.
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);
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.
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 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.
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.
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.
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.
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.
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.
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', '
}
If any of
the token strings are not matched by the token rule then tokSyncPreinit throws
a RuntimeError.
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.