To actually use the public properties defined in a class, that
class must be instantiated in an object. This done in one of two
ways: statically or dynamically. Static objects have a lifetime which
extends over the entire life of the program (it is not possible to
destroy a static object). Dynamic objects are allocated on the fly
and are destroyed when the runtime detects that no more references to
the object exist. The syntax by which static objects and dynamic
objects are created is slightly different. Note that the
create()
method and {}
completion operator of a static object are called before the
main()
procedure begins to run, whereas those of
a dynamic object are called at the point in the code where the new
dynamic object is created and initialized.
A static object is created in one of two ways: as a named static object, or as an unnamed static object which is assigned to a constant, a variable, or used in an expression. The syntax for creating a static object is nearly identical in both cases; the object name is simply omitted for the unnamed static object. Both static and dynamic objects may be created in one of two ways. The first way is this:
classname objname( create_args );
where classname is the name of a previously declared class,
objname is the desired name of the object, and create_args are
the arguments to be passed to the class's
create()
method. The objname and
terminating semicolon are omitted for unnamed static objects.
To initialize any of the public variables, use the syntax:
classname objname( create_args ) { assignments }
where assignments is a list of items of the form:
name = expr
Each name must be the name of a public variable in the class, and each expr must be a constant expression. For example, here's an adventure-language like construct:
room room1 { Lit = 1 LDesc = proc() {say("You are in room 1.\n");} SDesc = proc() {say("Room 1");} }
The object name is omitted for unnamed static objects. The named static object creation results in exactly the same behavior as using an unnamed static object as the initializer for a constant; for example, the following two statements result in identical behavior:
myClass myObj { myVar = 1; } const myObj = myClass { myVar = 1; }
The following two statements are NOT identical to each other, since the compiler thinks the second one is just creating a constant with the same value as the given class:
myClass myObj {} const myObj = myClass; // Does not create a static object!
If the create()
method does not require
arguments, then the parenthesis on the class name are optional.
Forward references to static objects may be created by specifying the
class name and the object name without any
create()
arguments or initializers; for example:
myClass myObj;
myObj is a forward reference to the object, and does not actually initialize the object (if a program with unresolved forward references is run a warning is generated).
Not all the properties of a static object need to be specified at
the same point in the source file; it is often useful to split out
similar functionality implemented in many objects into separate
source regions (for example, in an adventure, it is convenient to
have all of the room descriptions in one source file, and all of the
room actions in another source file). To accomplish this, use the
ellipsis ...
syntax:
location room1 { lit = TRUE ... } location room2 { lit = FALSE ... } /* some time much later in the input file */ ... room1 { sdesc = proc() {"Room 1";} ... } ... room2 { sdesc = proc() {"Room 2";} ... } /* much later still in the input file */ ... room1 { ldesc = proc() {"You are in room 1.\n";} } // Since we did not use ..., the room is complete at this point ... room2 { ldesc = proc() {"You are in room 2.\n";} } // Since we did not use ..., the room is complete at this point
If the last token before the closing brace of an object definition is the ellipsis, that indicates that the object will be continued. If the token before an object name is an ellipsis, it indicates that this is a continuing definition (and, if the object was not previously continued, an error is printed). Both the pre-ellipsis and post-ellipsis may be present, as shown by the middle example above.
Dynamic objects do not have a lifetime which extends over the life
of the program; instead, they are destroyed when OADL detects that
there are no more existing references to them. They also do not have
a name (the objname()
intrinsic will return
nil
if called with a dynamic object).
To create a dynamic object, use the new
operator, passing arguments to the create()
method of the class, optionally specifying the value of public
variables, and store the result in a variable:
var = new classname( create_args ) { public_assigns };
Here's the same example as above, but coded as a dynamic object:
var room1; proc main() { room1 = new room() { Lit = 1 LDesc = proc() {"You are in room 1.\n";} SDesc = proc() {"Room 1.\n";} }; }
During object destruction with a destroy
method, all references to non-constant dynamic values in the object
are replaced with nil
. The object itself is made
read-only. A temporary "deleted" property of the object is
set. Objects in this state are known as "zombie" objects.
Zombie objects may be resurrected if a new visible reference to them
is created during execution of the destroy
method:
z = nil resurrect = true class foo { public var a, b; public proc create(x) { a = @x; // a will be a non-static b = x; // b may be static if x is static "foo.create - returning ", self, '\n'; } public proc destroy() { "", self, ".destroy() {\n"; " self.readonly() = ", self.readonly(), '\n'; " oadl::deleted(self) = ", oadl::deleted(self), '\n'; " dynamic copy ", a, " vs static ref ", b, '\n'; "}\n"; if (resurrect) z = self; } } a = new foo("foo") foo.create - returning #OBJ(1) a = nil // The deleted() intrinsic prints "true" for the zombie object. // Zombie objects are read-only. oadl::gc() #OBJ(1).destroy() { self.readonly = true oadl::deleted(self) = true dynamic copy nil vs static ref foo } // Note that, since resurrect is "true", the destroy // method stashed a copy of that zombie object in the // global variable z // Set z.b to a dynamic copy of the string - it will get deleted // next time around z.b = @z.b // Examinine resurrected zombie z "z = ", z, "; z.b = ", z.b, '\n' z = #OBJ(1); z.b = foo "oadl::deleted(z) = ", oadl::deleted(z), '\n' oadl::deleted(z) = false // Now, without resurrection, the objects really get deleted resurrect = false z = nil oadl::gc() #OBJ(1).destroy() { self.readonly = true oadl::deleted(self) = true dynamic copy nil vs static ref nil }
The order in which destroy
methods is called
is non- deterministic. Back to Classes