OADL

Object Attribute Description Language

An interpreted object-oriented language designed for embedded
environments, especially computer games

Ross Cunniff
April, 2008

CONTENTS

1. Introduction

2. Lexical Elements

2.1. Tokens

2.2. Include Files

3. Constants

3.1. Constant Expressions

4. Variables

4.1. Variable Initialization

4.2. Variable Assignment

5. Expressions

6. Procedures

6.1. Arguments

6.2. Local Variables

6.3. Return Value

6.4. If Statement

6.5. Switch Statement

6.6. While Statement

6.7. For Statement

6.8. Do Statement

7. Classes

7.1. Private Elements

7.2. Public Constants

7.3. Public Variables

7.4. Protected Variables

7.5. Public Procedures

7.6. Operator Overloading

7.7. Inheritance

7.8. Multiple Inheritance

8. Objects

8.1. Static Objects

8.2. Dynamic Objects

9. Builtin procedures

10. External procedures

11. Glossary

12. Predefined symbols

13. OADL syntax

1. Introduction

Author's note: OADL was developed in the late 1990s. There is an implementation, but at this time I do not have time available to support it, so I am not making the code available. However, I thought some people might find the direction the original Adventure Definition Language had evolved into interesting, so I'm making this document available. -- Ross Cunniff, April, 2008

OADL is an object-oriented language which was designed for easy implementation for the compiler writer and interpreter, and easy learning and use by OADL programmers. OADL was especially designed for embedded use in computer games; for example, it can be used as an Adventure Definition Language for text adventures, or it can be used to define object actions in a DOOM-like game, or it can be used as a general- purpose language for solving miscellaneous problems.

This documentation presumes that the prospective OADL programmer has some familiarity with C, C++, and object-oriented programming in general.

At the root of OADL are objects. There are 4 "scalar" object types in OADL:

Null Integer Float Public

Null objects have no value, and every Null object is identical to every other Null object. Integer objects can store whole (non-fractional) numbers. Float objects can store floating point numbers ranging in magnitude from about 1e-38 to 1e38, and with about 6 decimal digits of precision. Public objects are used solely to look up public constants, variables, and procedures in Class objects.

In addition, there are several "compound" object types in OADL:

Proc Extern Class Object
String Array Pointer PackInt
PackFloat

A String is merely a list of ASCII characters. An Array is a list of arbitrary objects (including other Arrays). A Class is a class definition; an Object is an instance of a Class. A Proc is a procedure, a sequence of instructions to be performed. An Extern is a procedure implemented in a system-specific way; a Pointer is a pointer to some data object, again implemented in a system specific way (and only really meaningful to Extern procedures). The PackInt and PackFloat types are numeric array types which occupy much less memory space than the normal Array type; constant arrays with numeric-only values are automatically created as packed arrays. Operators are provided to pack and unpack arrays.

Compound objects can either be constant (read-only), or variable. All Proc objects and Class definitions are read-only. Strings and Arrays found in const declarations are also read-only. It is a runtime error to try to assign a value to an element of a read-only compound object.

The value of all variables and compound objects can be saved, or checkpointed, at any time during OADL program execution. A system-dependent number of checkpoints are available, but at least 4 will be provided. This facilitates the implementation of adventure-game constructs such as UNDO. In addition, the value of all variables and compound objects can be written to disk at any point, with their values restored by a subsequent invocation of the program (or later during the current execution of the program). This can be used to implement game SAVE and RESTORE functionality.

Return to top

2. Lexical Elements

OADL is a token-based compiler. It uses a greedy algorithm to create tokens; this means, for example, that the sequence of characters

        "==="
is interpreted as the token
        "=="
followed by the token
        "="
(which the compiler then flags as a syntax error).

White space (characters such as ASCII space, carriage return, line feed, form feed, horizontal tab, and vertical tab) is irrelevant, except as it separates tokens. Comments (delimited by /* and */) are considered whitespace, and otherwise ignored.

Return to top

2.1. Tokens

OADL recognizes the following punctuation as single-character tokens:

& | ^ ~ + -
* / % . , :
( ) { } [ ]
; <> ! =

In addition, OADL recognizes the following two-character tokens:

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

OADL also recognizes the following three-character tokens:

<<=>>=

There are several "composite" token types. The first is a Character Constant token, expressed as a single character between two single quote marks, thus:

        'x'

A String Constant token is similar, expressed as zero or more characters between two double quote marks, thus:

        "abcdefg"

In both Character Constant tokens and String Constant tokens, the character \ has special significance. Just as in C and C++, it is an "escape" character, which alters the interpretation of a number of characters following. Here is the complete list of escapes recognized by OADL:

\0 ASCII NUL character
\a ASCII bell character
\b ASCII backspace character
\f ASCII formfeed character
\n ASCII linefeed character
\r ASCII carriage return character
\t ASCII horizontal tab character
\v ASCII vertical tab character
\xNN 2-digit ASCII character in hexadecimal
\' Non-terminating single quote
\" Non-terminating double quote
\\ The backslash character
\any The given character

In OADL, an Integer Constant token takes one of two forms: a hexadecimal constant of the form 0xNNNN, where NNNN is a sequence of one or more hexadecimal digits (the numbers '0' through '9' and the letters 'a' through 'f' and 'A' through 'F'). Otherwise, an Integer Constant token is a decimal number expressed as one or more digits in the range '0' through '9'. Here are some examples:

0x1000The number 4096, in hex
123The number 123, in decimal

A Floating Point Constant token in OADL is distinguished from an Integer Constant token by one of two things: either it has an exponent part (the character 'e' or 'E' followed by a signed integer exponent), or it has a fractional part (the charater '.' followed by the digits comprising the fraction), or it has both. Here are some examples of Floating Point Constant tokens:

3.141592653589An approximation to PI
1e38About the biggest number representable
1e-38About the smallest number representable
.0Zero

OADL Identifier tokens consist of an alphabetic character or the characters '_' or '$', followed by zero or more alphabetic characters, the characters '_' or '$' or decimal digits (from '0' to '9'). Here are some example Identifier tokens:

Main$foomy_housex1

Note that OADL is case-sensitive; that is, the identifiers Main, main, mAIn, and MAIN are all unique.

OADL reserves several keyword Identifiers for syntactic purposes. This is the complete list:

breakcaseclassconst
continuedefaultdoelse
externforifinclude
protectedpublicnewproc
returnswitchvarwhile

Keywords may not be used as variable, class, object, or procedure names in OADL.

Return to top

2.2. The Include Statement

It is often convenient to break up a program into several source files; in that case, a means is provided to temporarily redirect the OADL compiler's input from another file. That means is the include statement, and its syntax is:

        include "filename"

Tokens are then read from the given filename until it has been exhausted; at that point, lexical analysis returns to the current file.

It is implementation-dependent how many nested levels of includes are supported; however, all implementations will support at least 4 levels.

Return to top

3. Constants

OADL has a several constant types, including Integers, Floats, Characters, and Strings. These constants may be used in many contexts, including expressions and initializations. The programmer may create named constants for later use, by using the const keyword followed by a comma-separated list of constant declarations. For example, the statement:

        const
            pi = 3.141592653589,
            euler = 2.7182;

creates two named constants, pi and euler, both floating-point numbers. Character constants are considered to be integers with the value being the ASCII code of the given character; for example:

        const
            three = '3';

creates the named constant 'three' which has the value 51, the ASCII code for '3'. Named string constants may also be created in this way; for example:

        const
            Name = "Ross Cunniff";

One more kind of constant exists, the Array constant, which creates a list of constants consisting of zero or more items between a pair of braces; for example:

        const
            Cards = {
                "A", "2", "3", "4", "5", "6", "7", "8",
                "9", "10", "J", "Q", "K"
            };

OADL constants may have one of three different scopes: global, class-private, and class-public. Class-private and class-public constants will be described below. Global constants are visible to all of the program elements which follow their declaration.

OADL predefines several constants for convenient use by the programmer. They are the Type constants:

NullIntFloatPublic
ProcExternClassObject
StringArrayPointerPackInt
PackFloat

and the Null object:

        nil

Return to top

3.1. Constant Expressions

A constant expression is simply an expression which has a constant value (see the section below, "Expressions", for more information on OADL expressions).

If all of the components of an expression are constants, then the entire expression is a "constant expression" and may be used any place a constant may be used. Here are some examples of constant expressions:

        const
            FirstName = "Ross",
            LastName = "Cunniff",
            FullName = FirstName >< " " >< LastName;

        const
            OnePlusTwo = 1 + 2;

        const
            Names = { "Fred", "Mary", "Ted" },
            Mary = Names[1];

Note that function calls and public references are the two exceptions to this rule: they are (almost) never constant expressions, with the exception of built-in functions, thus:

        const
            Array = array(10),
            Label = string(25);

Note also that unnamed proc and static object declarations are constants and may be used in constant and variable initializers; see the sections on procedures and objects for more information.

Return to top

4. Variables

Syntactically, variables are very similar to named constants. The difference is that their value may change during the process of program execution. OADL is what is known as a "dynamically-typed" language; so even the type of the variable may change during the process of program execution. Variables are declared with the var keyword; for example:

        var
            Weight, Height;

OADL variables have 5 possible scopes: global variables, class-private variables, class-public variables, local variables, and procedure arguments. Like global constants, global variables may be referenced and modified by any program element which follows their declaration. The other kinds of variables will be documented below.

Return to top

4.1. Variable Initialization

All kinds of variables (except arguments) may be initialized by constant expressions; the syntax is very similar to named constant declarations. For example:

        var
            Weight = 82, Height = 183;

The difference between this and a named Constant is twofold. First, an expression containing a variable references is NOT a constant expression. Second, the value of the variable is subject to change as the program executes.

Variables can also be initialized to an unnamed procedure using the same syntax as constants:

        var name = proc( args ) { statements }

Return to top

4.2. Variable Assignment

To change the value of a variable, an assignment statement must be executed. Assignment statements are generally found in procedures (see below), and come in a few flavors.

First, simple assignment is of the form:

        var = value;

The var may be a global variable, a local variable, an argument, a class variable in the current class, or a public variable in the current class. In any case, the old value is discarded, and the new value put in its place. The value may be any expression, constant or otherwise.

Elements of arrays and strings may be replaced using the syntax:

        var[index] = value;

Again, this discards the selected element and replaces it with the given value. Note that OADL arrays are indexed from 0, so in an N-element array, the maximum index is N-1. If the element at a given index is itself an array or string, double indexing is possible, and so on. For example, executing:

        var
            Table = { "Mary", "Sam" };

        Table[1][0] = 'P';

would result in Table containing { "Mary", "Pam" }.

Note that when compound objects (classes, objects, strings, and arrays) are used as the right-hand-side of an assignment statement, only a pointer, or "handle", for the object is assigned. That is, it is possible to have more than one variable referring to a compound object. For example, executing:

        var
            Str1 = "Fred",
            Str2;

        Str2 = Str1;
        Str2[0] = 'D';
        say( Str1 );

results in "Dred" being printed. The exception to this rule is when constant arrays, strings, or objects are assigned; in that case, a copy of the object is created. For example, executing:

        for( i = 0; i < 3; i += 1 ) {
            val = { 1, 2, 3 };
            say( "{", val[0], " " );
            val[0] = i;
            say( val[0], "} " );
        }

would result in {1 0} {1 1} {1 2} being printed, since a pointer to a copy of the array { 1, 2, 3 } was actually placed in the variable val.

Finally, public variables in other objects may be assigned to using the "dot" operator. See the Class and Object sections below for a full explanation:

        class foo {
            public var ack = 3;
        }

        foo bar;

        proc main()
        {
            bar.ack = 4;
        }

As a convenience, OADL allows "shortcut" assignments, where the left-hand side is modified arithmetically by the right-hand side. The complete list of shortcut assignments is:

        +=      -=      *=      /=      %=
        &=      |=      ^=      <<=     >>=

For example, suppose you want to increment a variable by one. You could do it longhand:

        i = i + 1;

Or you could use a shortcut assignment:

        i += 1;

This is really handy if the left hand side has a lot of complicated things going on:

        foo.bar[3].ack[9] += 5;

Note that shortcut assignments generally execute more efficiently than the equivalent longhand assignment statement.

Return to top

5. Expressions

The operators defined by OADL are as follows (in precedence order):

. [ ] ( )Public ref, array indexing, function call
~ ! -Bitwise NOT, logical NOT, negate
* / %Multiplication, division, modulus
+ -Addition, subtraction
<< >>Left shift, right shift
< > <= >=Less, Greater, Less or Equal, Greater or Equal
== !=Equal, Not-equal
&Bitwise AND
^Bitwise XOR
|Bitwise OR
&&Logical AND (pseudo-op)
||Logical OR (pseudo-op)
><Concatenation of two values

Note that Logical AND and Logical OR are pseudo-ops; that is, they are not overloadable and both implement "short-circuit" semantics. Specifically:

        ( 0 && anything ) == 0
        ( 1 && anything ) == anything
        ( 0 || anything ) == anything
        ( 1 || anything ) == 1

This can be useful if an expression you are evaluating or a function you are calling has an error condition you could avoid by placing it in a short- circuit evaluator; the following code:

        if( (i < 0) || (i >= length(a)) || (a[i] == 10) ) {
            say("a[i] == 10 (or i is out of range)\n");
        }

will not dereference the array a outside its bounds. Finally, the logical operators treat 0 as FALSE, and non-zero as TRUE. This can lead to different arithmetic results than bitwise AND and OR; for example,

        i1 = 2 && 3;
        i2 = 2 & 3;

will set i1 to 1 (TRUE) but i2 to 2 (which is the bitwise AND of 2 and 3).

There are some strict rules about the types of values that may be used in expressions (note that these rules do not apply when an operator is overloaded by a class; see Operator Overloading for more details):

OPERATOR LHS RHS RESULT
. Class Public any
[ ] String Int Int
[ ] Array Int any
( ) Proc   any
~   Int Int
!   Int Int
-   numeric numeric
* numeric numeric numeric
/ numeric numeric numeric
% Int Int Int
+ numeric numeric numeric
- numeric numeric numeric
<< Int Int Int
>> Int Int Int
< numeric numeric Int
> numeric numeric Int
<= numeric numeric Int
>= numeric numeric Int
== any any Int
!= any any Int
& Int Int Int
^ Int Int Int
| Int Int Int
&& Int Int Int
|| Int Int Int
>< Str,Arr Str,Arr Str,Arr

Those operators whose operand or result types are labeled "numeric" automatically do type conversion on Int and Float operands if the types are mixed. That is, if one operand is an Int and the other is a Float, they are both converted to Float and the result is a Float.

Those operators whose operand or result types are labeled "any" may take any object type as an operand. The == and != operators warrant further discussion. If their operands are both numeric, then the same type conversion listed above is performed. Otherwise, if their types differ, the values are considered "not equal" to each other. If their types are the same, then the equality test is done character-by-character for String objects, and only on the root of a compound object such as an Array. For example, the following program:

        proc main()
        {
            var a = "hi", b = "hi";
            say( a == b );
        }

will print '1' (as you might expect); however, the following program:

        proc main()
        {
            var a = {1,2,3}, b = {1,2,3};
            say( a == b );
        }

will print '0', since a and b are different compound objects.

Return to top

6. Procedures

There are three scopes of procedures: global procedures, class-private procedures, and class-public procedures (sometimes referred to as "methods"). Global procedures are declared outside the scope of any class declaration, and may be called by any other procedure. Class-private procedures are only directly visible from within a class definition. Public procedures are visible globally; however, they have special semantics with respect to object-oriented programming.

Normal procedures are constant; that is, given a declaration of a procedure, the instructions it executes never change. Note, however, that variables may hold the address of a procedure, effectively giving a kind of "dynamic procedure".

Procedures are declared with the proc keyword. They may be defined at the same time, or they may be merely declared, with definition following later. This facilitates both top-down programming and circular recursion. Here is an example of a declaration without a definition:

        proc
            foo;

If, at runtime, no definition of foo has been found, then a runtime error will be produced.

The definition of a procedure consists of three parts: the name of the procedure, the arguments it takes, and the instructions comprising it. Any of these may be omitted under certain circumstances. Here is an example which has all three parts:

        proc fact( n )  /* Procedure named fact, with one argument, n */
        {
            /* Here come the instructions! */
            if( n > 1 ) {
                var
                    next;       /* A local variable */
                next = n - 1;
                return n * fact( next );
            }
            else {
                return 1;
            }
        }

As this example indicates, procedures may return values, and they may call themselves recursively.

The name of the procedure may only be omitted when the procedure is a component of a constant expression; for example, the following statement assigns an unnamed procedure to a variable:

        a = proc() {say( "Hi, mom!\n" );}

Note that the normal named procedure declaration is semantically identical with a constant initialized to an unnamed procedure; that is, the following two statements result in the same behavior:

        proc a() {say( "Proc a\n" );}
        const a = proc() {say( "Proc a\n" );}

The statements encountered inside a procedure are one of four things:

In order to call a procedure, merely place the name of the procedure (or an expression evaluating to a Proc object) followed by the (possibly empty) list of arguments enclosed in parentheses and separated by commas, thus:

        foo( a, b, c );

Note that you *must* supply the argument list, even if empty, for OADL won't recognize a statement like this in the body of a procedure:

        foo;

Return to top

6.1. Arguments

As in most languages, you can pass arguments to OADL procedures. To declare the arguments to a procedure, merely list them in between the parentheses after the procedure name, thus:

        proc foo( a, b, c )
        {
        }

Note that it is valid to have a procedure which takes NO arguments; here's how you would define such a procedure:

        proc foo()
        {
        }

The list of arguments is specified only in the definition of the procedure, not in a declaration. That is, the statement:

        proc foo( a, b, c );

is illegal.

Note that in OADL, most procedure arguments are by-value. The exceptions are composite values such as objects, strings, and arrays. The object, string, or array itself may not be changed, but its contents might. For example:

        proc mod( a )
        {
            a[0] = 'H';
        }

        proc main()
        {
            var
                Name = "Sam";

            mod( Name );
            say( Name );
        }

will print Ham, not Sam. However, the program:

        proc mod( a )
        {
            a = "Ham";
        }

        proc main()
        {
            var
                Name = "Sam";

            mod( Name );
            say( Name );
        }

will print Sam, not Ham.

Note also that procedure argument names hide things of the same name outside the procedure; this can lead to difficult to find bugs. For example:

        var
            a = 3;

        proc foo( a )
        {
            a = 4;
            say( a );
        }

        proc main()
        {
            say( a );
            foo( 3 );
            say( a );
        }

will print the three numbers 3 4 3, in order, since the modification of the argument named a in procedure foo won't affect the global variable of the same name.

Finally, note that the nargs() and arg() built-in procedures may be used to facilitate both argument checking as well as variable-length argument lists. The nargs() built-in returns the number of arguments passed to the current procedure. The arg() built-in returns the value of the n'th argument. As an example, the following procedure adds together a list of numbers:

        proc addNums()
        {
            var
                i, n, result;

            n = nargs();
            result = 0;
            for( i = 0; i < n; i += 1 ) {
                result += arg( i );
            }
            return result;
        }

Return to top

6.2. Local Variables

OADL procedures may also have local variables. These are temporary locations used as intermediate values in calculations - their value is undefined as soon as the procedure exits. To declare local variables, use the var keyword:

        proc foo()
        {
            var a;
        }

Local variables have no defined value until they're initialized. As a convenience, you may include a constant expression as the initial value of the variable, thus:

        proc foo()
        {
            var a = 3;
        }

Just like procedure arguments, local variables hide global items of the same name, leading to the same difficulties. It is an error to declare a local variable of the same name as one of the procedure arguments.

Local variables remain visible until the end of the block in which they were defined; for example, the following program is illegal:

        proc foo()
        {
            if( true ) {
                var b;
                b = 3;
            }
            say( b );
        }

since the variable b is not defined after the closing brace of the if statement.

Note that local variables need not be the first statement in the block; the following is a legal code fragment:

        proc foo()
        {
            for( var i = 0; i < 10; i += 1 ) {
                say( i );
            }
        }

Return to top

6.3. Return Value

All OADL procedures return a value, whether or not an explicit return statement is included in them. If no return statement is given, the procedure returns the Null object, nil. To return a value, execute the return statement:

        return value

The value can be any expression (even nil). The procedure is immediately exited, and the calling context resumes execution.

Return to top

6.4. If statement

The if statement executes the associated statement if and only if its condition is true. The syntax of the if statement is:

        if( cond ) statement

The statement may either be a semicolon-terminated single statement, or it may be multiple statements surrounded by { and }. The condition can be any expression. The Null object (nil) and the Int 0 and the Float 0.0 all evaluate to false; all other values evaluate to true. If you wish to have a different statement executed when a condition is false than when it is true, use the else clause of the if statement:

        if( cond ) statement else statement

Again, compount statements can be formed using { and }. Note that an ambiguity potentially exists with nested if-else statements; in the construct:

        if( cond1 )
            if( cond2 )
                statement1
            else
                statement2

does the else belong to if( cond1 ) or if( cond2 )? OADL resolves this ambiguity by binding the else clause to the immediately preceeding if statement, a common practice in programming languages.

Return to top

6.5. Switch Statement

The switch statement evaluates an expression against multiple possible values, executing statements associated with the value which matches. The syntax of the switch statement is:

        switch( expr ) { cases }

where the expr is any expression, and the cases are of the form:

        case exprlist : statements

or

        default : statements

The exprlist is a list of comma-separated constant expressions; the statements are a list of semicolon-terminated statements.

The expression in the switch is only evaluated once; this is only really important if there are side effects (for example, a procedure call which sets global state).

Note that, although arbitrary constants are allowed in the cases, unexpected results might occur with compound objects like Arrays. For example, the following statement almost never does something useful:

        switch( Arr ) {
        case {1,2,3} : say( "123" );
        case {4,5,6} : say( "456" );
        }

since, even if Arr should point to some instance of {1,2,3}, it won't be the *same* array as the {1,2,3} in the case clauses. Strings, however, may be freely used in switch statements, and operate as might be expected.

Unlike C and C++, multiple cases may be specified in a single case; for example

        switch( i ) {
        case 1, 3, 5, 7, 9 : say("i is odd\n");
        case 0, 2, 4, 6, 8 : say("i is even\n");
        }

Also unlike C and C++ switch statements, "fallthrough" case statements can not occur, and therefore break terminations are unnecessary. This allows a break in a switch statement to terminate loop execution.

Return to top

6.6. While Statement

The while statement is the simplest loop iterator in OADL. The syntax of the while statement is:

        while( condition ) statement

Just like if-else, the statement may be a compound statement surrounded by { and }. The boolean true-or-false of the condition is evaluated exactly like an if statement, but it is done once at the start of each loop iteration. If it is necessary to terminate the loop early, use the break statement:

        break

which is usually encased in an if statement:

        if( cond ) break;

If you wish to skip the rest of the instructions in a loop, but want to go on with subsequent iterations, use the continue statement:

        continue

Again, this is usually encased in an if statement:

        if( cond ) continue;

Return to top

6.7. For Statement

The for statement is nearly identical to the C/C++ equivalent. It has a group of comma-separated initializations; a termination condition; and a group of comma-separated increment expressions. Any or all of these may be empty. If the condition is omitted, then the loop will never terminate (unless a break or return statement is executed inside the loop):

        for( init; condition; incr ) statement

The statement, again, can be multiple statements inside { and }. The condition follows the same rules as the if/else statement. Just like the while. statement, break and continue alter the iteration of the loop. Note that the incr statements *will* be executed if a continue is executed.

Return to top

6.8. Do Statement

OADL provides the do-while statement to provide for end-of-loop structured loops. The syntax is:

        do statement while( condition )

Multiple statements may be grouped with {. and }. The condition follows the same rules as the if/else condition. The break and continue statements are meaningful inside the do statement. Note that a do loop always executes at least one time since the condition is evaluated after all of the statements.

Return to top

7. Classes

OADL classes are substantially different from both C structs and C++ classes. Unlike C structs, OADL classes allow methods (public procedures) to be defined. Unlike C++ classes, all methods are essentially virtual.

Similar to C and C++, though, 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 (it is necessary, though, to include the complete class definition in order to sublass it).

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

        class namelist;

To define a class in a way which will allow subclassing, use the class definition:

        class name {
            proplist
        }

The proplist is a sequence of constant, variable, and procedure definitions. 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 association of public property with actual item can only be done inside a class definition. The syntax for declaring a public property or properties is:

        public namelist;

Note that there is not necessarily any association of those public properties with any particular class; this is simply a forward declaration of propertis that might be part of some class. Referring to a non-existent public property of a class or object results in a runtime error.

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, it is exactly the same as the public keyword.

Return to top

7.1. Private Elements

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

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

Private elements are very useful if you want to have the freedom 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. Note that 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.

Return to top

7.2. 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;
        }

Two special public constants are defined for all objects and classes in OADL. They are self, and parent. The public constant self refers to the object itself; this is handy if the object needs to pass itself to another routine. The public constant parent 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. Note that other procedures can refer to the public constants self and parent; using self is typically useless, since:

        obj.self == obj

however, the parent public constant is often very useful:

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

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

Return to top

7.3. 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 box public, you could declare its position as a public variable:

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

Be very careful, though, when using public variables: you have exposed the variable as an interface to your class, and might have trouble later if you decide that you don't want others messing with those properties.

Return to top

7.4. 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 you do not want to allow arbitrary code to change the temperature, you should use a protected variable:

        class thermal {
            protected var temp = 0;
            public proc create(initTemp) {
                temp = initTemp;
            }
        }

Note that the create method modifies the temp variable; however, any attempt by an outside procedure to modify temp will result in a runtime error.

Return to top

7.5. Public Procedures

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; }
        }

All classes in OADL have two predefined public procedures: create, destroy, and one predefined operator, the completion operator{}. The create public procedure is called when a dynamic object is created. The destroy public procedure is called when OADL detects that no more references to an object exist, and is about to free that object. The completion method is called at the completion of static public variable initialiation (see below).

Typically, classes do not need to provide their own create and destroy routines, as the default ones do a pretty darn good job of it.

Return to top

7.6. 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, class member reference, array indexing, and procedure call:

        "&&"     "||"       "."     "[]"    "()"
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) {
                var result;
                result = new complex(real + rhs.real, imag + rhs.imag);
                result.operator {}();
                return result;
            }
        }

Binary operators are passed one argument, the right-hand argument, as in the example (the left-hand argument is implicitly self). Unary operators (such as "~", "!", "-", etc.) are passed no arguments. Note that the "-" operator is both a binary and a unary operator; classes overloading this operator should use the nargs builtin to distinguish between the two, thus:

        operator - (rhs) {
            var result;
            if (nargs() > 0) {
                result = new complex(real - rhs.real, imag - rhs.imag);
            }
            else {
                result = new complex(-real, -imag);
            }
            result.operator {}();
            return result;
        }

Note that it is possible to call an operator method without using the default expression syntax (in fact, this is the only way one may call the completion method {}). The name of an overloaded operator is "operator op" where op is one of the possible overloaded operators. See the above for one example of calling an overloaded operator in this way.

Return to top

7.7. 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. Note, however, that the proplist may not change an element to an element of a different kind; for example, you may not change a constant to a variable, 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.

Return to top

7.8. Multiple Inheritance

To solve certain problems, 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 Aclass {
            public var a;
        }

        class Bclass {
            public var b;
        }

        class ABclass(Aclass,Bclass) {}

In the example given, the class "ABclass" has the public variables from both Aclass AND Bclass.

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

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

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

        ABclass ab {}

        proc main()
        {
            ab.Describe();
        }

will print:

        b = 2

since the Describe public property from class Aclass was overidden by the Describe public property from Bclass.

Return to top

8. Objects

To actually use the public properties defined in a class, you must instantiate that class in an object. You can do this in one of two ways: statically or dynamically. Static objects typically have a lifetime which extends over the entire life of the program (it is generally a very bad idea to destroy a static object). Dynamic objects may come and go at the whim of the program. The syntax by which static objects and dynamic objects are created is very different. In addition, the completion operator {} of a class is automatically called for static objects, but not for dynamic objects. Finally, both the create() public procedure and {} operator of a static object are called before the main() procedure begins to run.

Return to top

8.1. Static Objects

You may create a static object 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; it's just that the object name is 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( create_args ) objname;

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 public procedure. Remember that the objname and terminating semicolon are omitted for unnamed static objects.

If you want to initialize any of the public variables, you can instead use the syntax:

        classname( create_args ) objname {
            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 either a constant expression (terminated by a semicolon) or a procedure definition. 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");}
        }

Again, remember that 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; }

Be very careful, though; the following two statements are NOT identical, 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;

If you have no arguments to pass to the create public procedure, then you may omit the parenthesis on the class name. Forward references to static objects may be created by only specifying the class name and the object name without any create arguments or initializers; for example:

        myClass myObj;

is only a forward reference to the object, and does not actually initialize the object (if you try to run a program with unresolved forward references a warning is generated).

Return to top

8.2. Dynamic Objects

Dynamic objects do not have a lifetime which extends over the life of the program; they exist at the whim of the programmer. They also do not have a name. And finally, you need to explicitly call the {} operator of a dynamically created object.

To create a dynamic object, simply execute the create routine of the class and store the result in a variable:

        var = classname( create_args );

You can then set public variables using normal syntax:

        var.prop = expr;

Here's the same example as above, but coded as a dynamic object:

        var room1;
        proc main()
        {
            room1 = room();
            room1.Lit = 1;
            room1.LDesc = proc(say("You are in room 1.\n");};
            room1.SDesc = proc(say("Room 1.\n");};
            room1.operator {}();
        }

Note carefully the semicolons at the end of the public variable assignments; they are simply normal assignment statements.

Dynamic objects will automatically be destroyed when all references to them have been eliminated.

Return to top

9. Built-in Procedures

OADL itself defines several builtin procedures. These mostly operate on array and string primitives, providing a base level of functionality needed in the language:

arg(n) Returns the value of argument number 'n'. Argument number 0 is defined to be the number of arguments given to the current procedure.
array(n) Create an array with n elements, initialized to nil.
concat(a,b) Concatenates two arrays or strings, returning a new array or string. Exactly the same as the >< operator.
length(a) Returns the number of elements in an array or string.
nargs() Returns the number of arguments passed to the current procedure.
pack(array) Creates a packed array from the given argument. If the array cannot be packed, it returns the original unpacked array.
string(n) Create a string with n characters, initialized to ASCII NUL.
subr(val,start,num) Returns the substring or subarray from the given value starting at index start and going for num items. Note that the data used by this substring or subarray is still part of the original string or array; hence, modifying its contents will modify the original object as well.
typeof(val) Returns the type of a value, for example, Int, Null, String, etc.
unpack(array) Creates an unpacked array from the given argument. If the array cannot be unpacked, it returns the original packed array.

Return to top

10. External procedures

External procedures are those which are defined in the runtime environment, but which are not part of the OADL machine by default. They are declared using the extern keyword:

        extern namelist;

The header file oadl.h predefines several external procedures which are part of the normal OADL runtime. They are documented below.

Return to top

10.1. Object Manipulation Procedures

token(str,tok) Extracts the next token from a string, and puts it in the string tok. Returns 0 if no more tokens are available, or 1 otherwise. The tokens will be truncated to the allocated length of tok.
str2num(str) Convert a string value to a numeric value
num2str(num) Convert a numeric value to a string value

Return to top

10.2. State Save/Restore Procedures

save(name) Saves all compound objects to the given filename. Returns -1 if the save failed, 0 if it succeeded, and 1 after a subsequent restore.
restore(name) Restore all compound objects from the given filename. No return value; if instructions after the restore are executing, an error occurred.
mark(n) Establishes checkpoint number n. At least 4 concurrent checkpoints are allowed. Returns -1 if an error occurs, 0 if the mark succeeds, and 1 if a subsequent unmark() is called on that mark.
unmark(n) Restores all compound objects and variables to be consistent with mark number n. Program control is returned to the location where mark number n was established. No return value; instructions after the unmark call are only executed if the unmark fails for some reason.

Return to top

10.3. Input/Output Procedures

say(val1,val2,...,valN) Prints out an ASCII representation of each value on the user's display. The only really useful things to print are strings and numbers; everything else will print out a somewhat cryptic identifier such as OBJ(000003F2).
read(str) Reads ASCII characters from the user's keyboard and places them in str. An end-of-line character will terminate the read, and place an ASCII NUL character at the final position in the string. If the string is filled up, no NUL character will be placed, and unread characters remain buffered by the operating system.

Return to top

10.4. Miscellaneous Procedures

setjmp(arr) Sets the target for a later longjmp() call. This is an inter-procedure goto. This returns 0 if it succeeds, and 1 after a subsequent longjmp(). The arr argument must be an array of at least 2 elements, which will be overwritten.
longjmp(arr) Transfer program control back to a previously setjmp()'d location.
exit(val) Terminates program execution, returning the given integer value to the operating system.

Return to top

11. Glossary TBD

argument An argument is a value which is given to a procedure (or program) to operate on. For example, in the expression "say( a )", "a" is the only argument to the "say" procedure.
array

An array is a sequential list of values. In OADL, arrays need not contain values of all the same type. For example, the following is a valid OADL array:

{1,2,"Hi",myClass}
ASCII American Standard Code for Information Interchange - the venerable 8-bit standard for the representation of characters. The ASCII table gives a numeric value for each possible character in the defined character set; for example, the letter 'a' has the ASCII value 97.
class
constant
expression
floating point
identifier
inheritance
integer
public
multiple inheritance
object
procedure
scalar
token
variable

Return to top

12. Predefined symbols

This section reiterates the complete list of predefined OADL symbols.

Keywords:

        break           case            class           const
        continue        default         do              else
        extern          for             if              include
        public          new             operator        proc
        return          switch          var             while

Primitive Types:

        Null            Int             Float           Public
        Proc            Extern          Class           Object
        String          Array           Pointer

Builtin Procedures:

        array           concat          length          pack
        string          subr            typeof          unpack

Predefined Public Properties:

        create          destroy         parent

Miscellaneous:

        nil             self

Return to top

13. OADL Syntax

program         : /* NOTHING */
                | 'class' class_decl
                | 'var' var_decl
                | 'const' const_decl
                | 'include' STRING
                | 'public' public_decl
                | NAME obj_decl
                | 'proc' proc_decl
                ;

class_decl      : NAME proplist
                | NAME '(' NAME ')' proplist
                | names ';'
                ;

proplist        : '{' props '}'
                ;

props           : prop
                | props prop
                ;

prop            : 'public' oneprop
                | 'protected' oneprop
                | oneprop
                ;

oneprop         : 'var' var_decl
                | 'const' const_decl
                | 'proc' proc_decl
                | 'operator' operator proc_body
                ;

var_decl        : vars ';'
                ;

vars            : var
                | vars ',' var
                ;

var             : NAME
                | NAME '=' expr
                ;

const_decl      : consts ';'
                ;

consts          : const
                | consts ',' const
                ;

const           : NAME '=' expr
                ;

public_decl     : names ';'
                ;

names           : NAME
                | names ',' NAME
                ;

obj_decl        : names ';'
                | optargs name optprops
                ;

optargs         : /* NOTHING */
                | '(' args ')'
                ;

optprops        : /* NOTHING */
                | '{' objprops '}'
                ;
objprops        : /* NOTHING */
                | objprops objprop
                ;

objprop         : NAME '=' expr
                | NAME '=' 'proc' '(' names ')' body
                ;

proc_decl       : NAME proc_body
                | names ';'
                ;

proc_body       : '(' names ')' body
                | '(' ')' body
                ;

body            : '{' stmts '}'
                ;

stmts           : /* NOTHING */
                | stmts stmt
                ;

stmt            : var_decl
                | assign
                | call
                | ifstmt
                | whilestmt
                | forstmt
                | dostmt
                | switchstmt
                | returnstmt
                | breakstmt
                | continuestmt
                ;

operator        : '~' | '!' | '-'
                | '*' | '/' | '%'
                | '+'
                | '<<' | '>>'
                | '<' | '>' | '<=' | '>='
                | '==' | '!='
                | '&'
                | '^'
                | '|'
                | '><'
                ;

assign          : dotexpr assign_op expr ';'

assign_op       : '='
                | '+='
                | '-='
                | '*='
                | '/='
                | '%='
                | '&='
                | '|='
                | '^='
                | '<<='
                | '>>='
                | '{' '}'
                ;

call            : dotexpr ';' /* NOTE: dotexpr includes call syntax */
                ;

args            : /* NOTHING */
                | exprs
                ;

exprs           : expr
                | exprs ',' expr
                ;

ifstmt          : 'if' '(' expr ')' compound
                | 'if' '(' expr ')' compound 'else' compound
                ;

compound        : stmt
                | '{' stmts '}'
                ;

whilestmt       : 'while' '(' expr ')' compound
                ;

forstmt         : 'for' '(' init ';' limit ';' incr ')' compound
                ;

init            : /* NOTHING */
                | comma_stmts
                ;

limit           : /* NOTHING */
                | expr
                ;

incr            : /* NOTHING */
                | comma_stmts
                ;

comma_stmts     : comma_stmt
                | comma_stmts ',' comma_stmt
                ;

comma_stmt      : 'var' T_NAME T_EQUALS expr
                | dotexpr assign_op expr
                | dotexpr       /* This is to get calls in */
                ;

dostmt          : 'do' compound 'while' '(' cond ')' ';'
                ;

switchstmt      : 'switch' '(' expr ')' '{' cases '}'
                ;

cases           : /* NOTHING */
                | cases case
                ;

case            : 'case' exprs ':' stmts
                | 'default ':' stmts
                ;

returnstmt      : 'return' expr ';'
                ;

breakstmt       : 'break' ';'
                ;

continuestmt    : 'continue' ';'
                ;

expr            : orexpr
                | expr '><' orexpr
                ;

logorexpr       : logandexpr
                | logorexpr '||' logandexpr
                ;

logandexpr      : orexpr
                | logandexpr '&&' orexpr
                ;

orexpr          : xorexpr
                | orexpr '|' xorexpr
                ;

xorexpr         : andexpr
                | xorexpr '^' andexpr
                ;

andexpr         : eqexpr
                | andexpr '&' eqexpr
                ;

eqexpr          : relexpr
                | eqexpr '==' relexpr
                | eqexpr '!=' relexpr
                ;

relexpr         : shiftexpr
                | relexpr '<' shiftexpr
                | relexpr '>' shiftexpr
                | relexpr '<=' shiftexpr
                | relexpr '>=' shiftexpr
                ;

shiftexpr       : addexpr
                | shiftexpr '<<' addexpr
                | shiftexpr '>>' addexpr
                ;

addexpr         : mulexpr
                | addexpr '+' mulexpr
                | addexpr '-' mulexpr
                ;

mulexpr         : unaryexpr
                | mulexpr '*' unaryexpr
                | mulexpr '/' unaryexpr
                | mulexpr '%' unaryexpr
                ;

unaryexpr       : dotexpr
                | '~' dotexpr
                | '!' dotexpr
                | '-' dotexpr
                ;

dotexpr         : terminal
                | dotexpr '.' terminal
                | dotexpr '[' expr ']'
                | dotexpr '(' args ')'
                ;

terminal        : '(' expr ')'
                | '{' args '}'
                | STRING
                | INTEGER
                | FLOAT
                | CHARACTER
                | NAME
                | CLASSNAME optargs optprops
                | '.' NAME
                | 'proc' '(' args ')' body
                | 'new' NAME optargs
                | 'new' '(' expr ')' optargs
                | 'operator' operator
                ;

Return to top