Classes

OADL classes are similar to C structs and C++ classes, but there are some important differences. Unlike C structs (but like C++ classes), OADL classes allow methods (public procedures) to be defined. Unlike C++ classes, though, all methods are essentially virtual.

Also like C++, classes may define properties which are either private (the default - not visible to other classes or external functions) or public. Because of OADL's dynamic typing, it is not necessary to include a complete class definition in a header file in order to use it or subclass it.

To declare a class (without defining it), use the class declaration:

class namelist;

To fully define a class and its attribute, use the class definition:

class name {
    proplist
}

The proplist is a sequence of constant, variable, and procedure definitions. See the OADL Program Syntax for a description of the syntax.

Public Elements

Only the public properties are visible outside the class. Public properties are declared using the public keyword:

public const constlist
public var varlist
public proc proc

Public properties may also be defined outside a class; however, the default association of a public property with an actual value can only be done inside a class definition. The syntax for declaring a public property or properties is:

public namelist;

There is not necessarily any association of those public properties with any particular class; this is simply a forward declaration of properties that might be part of some class. Referencing a non-existent public property of a class or object results in the value nil. Any attempt to assign a value to non-existent public property results in a RangeCheck exception.

A subset of public properties exist in OADL; these are protected properties. Protected properties may be referenced outside their class definition, just like a public property. However, they may only be assigned a new value inside a class-public or class-private procedure. The syntax is very similar to the syntax for public properties:

protected var varlist

Although OADL accepts the protected keyword for const and proc declarations, in those cases, the effect is exactly the same as the public keyword.

The public names exist in a separate namespace from other program objects. OADL attempts to use the proper namespace based on context, but the program may override its choice by using the public keyword as the namespace in an expression:

proc create()
{
    "pubname(create) = ", pubname(public::create), "\n";
}

Private Elements

The private elements of a class are those elements which are not visible or accessible to outside procedures. To declare private elements in a class, merely insert a const, var, or proc declaration inside the class definition scope; for example:

class box {
    const MyName = "Boxy";
    var position, size;
    proc PrintMyName() { say(MyName); }
}

Private elements are very useful if it is necessary to change the underlying implementation of a class. Since no other procedure can even know that private data exists in a class, changing that private data can in no way affect those other procedures. References to private elements inside the class declaration look exactly like normal variable or constant references; the compiler keeps track of what is meant by each name.

Public Constants

Public constants are constants which are visible to the outside world, but not modifiable. For example, suppose that boxes, by default have a weight which is meaningful for other objects to refer to:

class box {
    public const Weight = 60;
}

One special public constant is defined for all objects and classes in OADL: parent. It refers to the parent class of a class or object, and is handy when a class wants to enhance a public procedure with some extra code, but wants to use the parent same public procedure of the parent class as well to do something. Other procedures can refer to the public constant parent

switch (obj.parent) {
case box :      /* Do something boxy */
case sphere :   /* Do something spherical */
        /* and so on */
}

A class which was created without a parent has a parent public constant of nil.

Public Variables

Public variables are variables which are visible to and modifiable by outside procedures. If, for example, outside physical forces might move a box around without having to call a public box method, its position could be declared as a public variable:

class box {
    public var pos = [0,0];
}

Be very careful, though, when using public variables: the variable is exposed effectively as an interface to the class. This might cause trouble later if the class definition is changed. This risk may be somewhat reduced by annotating the public variable with a type decoration:

    class box {
        public var pos : PackInt[2] = [0,0];
    }

    box box1()
    box1.pos = [1,2]
    box1.pos = nil
Illegal type

    // OADL does type conversions for conformant array assignments
    box1.pos = [1.2, 2.2]
    box1.pos
1 2

Protected Variables

Protected variables are variables which are visible to outside procedures, but only modifiable by class-private and class-public procedures. If, for example, the temperature of an object may slowly approach room temperature in a well-defined way, but arbitrary code must not be allowed to change the temperature, a protected variable should be used:

class thermal {
    protected var temperature = 0;
    public proc create(initTemp) {
        temperature = initTemp;
    }
    public proc warmup(degrees) {
        temperature += degrees;
    }
}

In this example, the create() method modifies the temp variable; however, any attempt by an outside procedure to modify temp will result in a runtime AccessCheck error.

Note that a class may implement the assign operator ":=" which, if provided, will be called when an attempt is made to assign to a protected variable. For data integrity reasons, the assign operator may only be implemented if all parent classes of a class also implement it:

    // Demonstrate implemenation of an assign operator
    class assign {
        protected var a, b;
        operator := (prop, val) {
            switch (prop) {
            case public::a : self.(prop) = val;
            default : throw oadl::AccessCheck;
        }
    }

    assign x()
    x.a = 10
    x.b = 20
Access failure

    // Demonstrate restriction on overriding the assign
    // operator to gain access to protected variables
    class foo {
        protected var a;
    }
    class bar(foo) {
        operator := (prop, val) {}
Access failure

Public Procedures (Methods)

Public procedures are also known as methods. They are procedures which execute as if they were inside the class: they have access to all internal class constants, variables, and procedures. The better way to do the box position example above is to use a public procedure to move the box, so that its internal representation might change later:

class box {
    var pos = [0,0];
    public proc move(x, y) { pos[0] = x; pos[1] = y; }
}

The token -> may be used as a synonym for the . operator. This is useful when lexical confusion might otherwise occur (for example, when an integer is the target of the method call):

$ cat foo.oad
proc main()
{
    var a = 3.iterate();
}
$ oadl foo.oad
File foo.oad line 3: ';' expected
    var a = 3.iterate();
---------------------^
$ cat bar.oad
proc main()
{
    // Use the -> operator to avoid confusion between a floating-point
    // constant and the method call
    var a = 3->iterate();
    "", a, '\n';
}
$ oadl bar.oad
0 1 2

Create, Completion {}, and Destroy

All classes in OADL have two predefined methods, create() and destroy(), and one predefined operator, the completion operator { }. The create method is called when a dynamic object is created. The destroy method is called when OADL detects that no more references to an object exist, and is about to free that object (see the section on Zombie Objects for more details). The completion operator is called at the completion of static public variable initialiation (see below). The completion operator is also called at the completion of with statements (see the section on with statements in the Procedures chapter for more information).

The completion operator is called with several arguments. The first argument is either false (if the object is only partially completed due to use of the ... syntax) or true. The rest of the arguments are all of the public names that were assigned inside the current brace pair. For example:

    class a {
        public var b; 
        operator {} (compl) {
            using namespace oadl;
            var i;
            for (i = 1; i < nargs(); i++) {
                "Public ", arg(i), " was set to ",
                                self.(arg(i)), "\n";
            }
            if (compl) "Completed.\n";
        }
    }

    a() aa { b = "foo" }
Public b was set to foo
Completed.

The global pseudo-constant self refers to the current object that the current topmost method call applies to; this is handy if an object needs to reference itself to another routine. For example:

    proc ack() 
    {
        "self = ", oadl::objname(self), "\n";
    }

    class foo {
        public proc create() {
            ack();
        }
    }

    foo() bar
self = bar

Typically, classes do not need to provide their own destroy() implementation since the default one is generally sufficient. The main reason a class might have a destroy() method is if the class holds references to objects which are not tracked by OADL (for example, operating system objects).

Operator Overloading

OADL, like many other object-oriented languages, supports operator overloading. This allows classes to implement their own versions of standard OADL operators. All operators except logical AND &&, logical OR ||, and object public property reference . are overloadable. A class overloads an operator via the operator keyword, thus:

class complex {
    public var real, imag;
    public proc create(r, i) {
        real = r;
        imag = i;
    }
    operator + (rhs) {
        return new complex(real + rhs.real, imag + rhs.imag);
    }
    operator \+ (lhs) {
        return new complex(lhs.real + real, lhs.imag + imag);
    }
}

Binary operators are passed one argument - the other operand in the expression. If it is neccessary to distinguish the case where an object is the left operand from the case where an object is the right operand, "right-binding" versions of the operators are used. The names of each "right-binding" operator is the same as the name of the corresponding "left-binding" operator, but with a backslash in front of the operator:

\| \^ \& \< \> \+ \-
\* \/ \% \== \!= \<= \>=
\<< \>> \=> \~= \**

If no "right-binding" operator is present, both left- and right-operand overloaded operators call the normal operator method.

Unary operators (~ and -) are passed no arguments. Since the minus operator - is both a binary and a unary operator, a special operator name is provided for the unary version of minus. This name is !-. For example:

operator - (rhs) {
    return new complex(real - rhs.real, imag - rhs.imag);
}
operator \- (lhs) { // Right-binding version
    return new complex(lhs.real - real, lhs.imag - imag);
}
operator !- () () // Unary version
    return new complex(-real, -imag);
}

It is possible to call an overloaded operator method without using the default expression syntax. The name of an overloaded operator is operator op, where op is one of the possible overloaded operators. Since the ! operator can natively operate on expressions of any type, the "operator !" syntax is the only way this operator may be overloaded. As a shorthand, the backtick may be used instead:

    class factorial {
        operator ! (lhs) {
            if (lhs > 1) {
                return lhs * self.`!(lhs-1);
            }
            else {
                return lhs;
            }
        }
    }


    a = new factorial()
    "7! = ", a.`!(7), '\n'
7! = 5040
}

Procedure call overloading is a little different than general operator overloading. Instead of an explicit list of arguments the nargs() and arg() intrinsics must be used to access the arguments of the overloaded procedure:

    class procClass {
        public operator () () {
            var i;
            for(i = 0; i < oadl::nargs(); i++) {
                "arg(",i,") = ", oadl::arg(i), '\n';
            }
        }
    }
    a = new procClass()
    a(1,2,3)
arg(0) = 1
arg(1) = 2
arg(2) = 3

Array indexing overloading has two operators involved - index references, and indexed assignments. Therefore, there are two different operators: [] and [=]. The index reference operator [] works as might be expected - the argument list is the list of indexes to be looked up. The index assignment operator [=] takes at least two arguments. The last argument is the value that should be assigned. The remaining arguments are the multi-dimensional index:

    class arrClass {
        var arr;
        public proc create(n,m) { arr = [n,m].iterate(); }
        public operator [] (i0,i1) { return arr[i0,i1]; }
        public operator [=] (i0,i1,val) { arr[i0,i1] = val; }
        public proc print() { "", arr, '\n'; }
    }
    arrClass arr(4,5)
    arr[2,3] = 100
    arr[2,3]
100

    arr.print()
 0  1  2   3  4
 5  6  7   8  9
10 11 12 100 14
15 16 17  18 19

The flattened array index operators #[] and #[=] are supported in a similar way.

Inheritance

OADL fully supports class inheritance. Inheritance creates a new class derived from an existing one. The new class inherits all of its private constants, variables, and procedures, as well as all of its public properties. The new subclass may then change or add to that list to customize itself. To declare a class which inherits from another class, use the following syntax:

class name( parent ) {
    proplist
}

The proplist may change the initial value of variables, constants, and public properties, and it may add new ones. The proplist may not change an element to an element of a different kind; for example, the program may not change a const to a var, or a public property to a private element. Here is an example of inheritance:

class PhysObj {
    var
        Pos = { 0, 0, 0 },
        Mass = 1.0,
        Momementum = { 0, 0, 0 };
    public proc Accelerate( force )
    {
        Momentum[0] += force[0];
        Momentum[1] += force[1];
        Momentum[2] += force[2];
    }
    public proc Move( dT )
    {
        Pos[0] += dT * Momentum[0] / Mass;
        Pos[1] += dT * Momentum[1] / Mass;
        Pos[2] += dT * Momentum[2] / Mass;
    }
}

class SphereObj( PhysObj ) {
    var
        Radius = 1.0;
    public proc create( radius )
    {
        Radius = radius;
    }
}

The SphereObj is a subclass of PhysObj, and has all of its properties, including Pos, Mass, and Momentum, and all of its public properties. It defines a new property, Radius, and its create() method is called with the desired radius.

Multiple Inheritance

To solve certain problems (for example, in an adventure, to define a magical object which provides light and unlocks a door), it is often useful to have a class which is derived from more than one parent class. OADL supports a multiple inheritance capability which helps when these issues come up. To declare a class which inherits from multiple parents, simply list all of the parents in the class declaration, thus:

class litObjClass {
    public const lit = 1;
}

class keyObjClass {
    public const unlocks = 1;
}

class litKeyClass(litObjClass,keyObjClass) {}

In the example given, the class "litKeyClass" has the public and protected variables and constants from both litObjClass AND keyObjClass.

When creating classes with multiple inheritance, properties from parent classes later in the list of parent classes override those of earlier classes (except for the parent public property). For example:

    class Aclass {
        var a = 1;
        public proc Describe() { "a = ", a, "\n"; }
    }

    class Bclass {
        var b = 2;
        public proc Describe() { "b = ", b, "\n"; }
    }
    class ABclass(Aclass,Bclass) {}

    ABclass ab {}
    ab.Describe()
b = 2

In this example, the Describe public property from class Aclass was overidden by the Describe public property from Bclass. The reason for this is that, conceptually, inheriting from multiple classes is similar to inheriting from the first class in the list, and then adding the extra declarations. For example, the code above is very similar to this code:

    class Aclass {
        var a = 1;
        public proc Describe() { "a = ", a, "\n"; }
    }

    class ABclass(Aclass) {
        var b = 2;
        public proc Describe() { "b = ", b, "\n"; }
    }

    ABclass ab {}
    ab.Describe()
b = 2

The difference between this and multiple inheritance is that the Aclass and Bclass classes still exist as part of the ABclass. The following program is very hard to write with single inheritance, since the var a is in both Aclass and Bclass:

    class Aclass {
        var a = 1;
        public proc aDescribe() { "a = ", a, "\n"; }
    }
    class Bclass {
        var a = 2;
        public proc bDescribe() { "a = ", a, "\n"; }
    }
    class ABclass(Aclass,Bclass) {}

    ABclass ab {}

    ab.aDescribe()
a = 1
    ab.bDescribe()
a = 2

Dynamic Runtime Classes

Classes may be created dynamically at runtime. These classes do not have an inheritance tree - they always have a parent property of nil. A dynamic runtime class is created via the new Class(className, pubPropList) syntax. The className is the name of the new class. The pubPropList is a List of pub, value pairs. Each of the Public pub items is made a new public var of the new class. The corresponding value is used as the initial value of that variable in the class.

Accomanying dyamic runtime classes are dynamic runtime publics. These are created via the new Public(pubName) syntax. The pubName is the name of the new public.

An example of both dynamic publics and dynamic classes is:

public pub2;

proc main()
{
    // Create a couple of new publics
    var pub0 = new Public("pub0");
    var pub1 = new Public("pub1");
    // Create a new class using those, plus an existing public
    var cls = new Class("cls", {pub0, 123, pub1, "hello", pub2, 3.14});
    "New class:\n";
    forall (cls.(pub)) {
        "", pub, ": ", cls.(pub), '\n';
    }
    "\n";

    // Note that classes created with "new Class" are *not* readonly
    cls.(pub0) = 456;

    // Create an instance of that class
    var obj = new cls();
    "New instance:\n";
    forall (obj.(pub)) {
        "", pub, ": ", obj.(pub), '\n';
    }
}

The program above produces the following output:

New class:
parent: nil
pub2: 3.14
pub0: 123
pub1: hello

New instance:
parent: cls
pub2: 3.14
pub0: 456
pub1: hello

Dynamic classes are garbage collected when all references to them are removed. However, dynamic publics are not; they persist for the duration of program execution.

Back to Arrays and Dictionaries

Continue to Objects (Instances)

Return to Introduction