Procedures

Overview

There are three scopes of OADL 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.

Procedures are constant; that is, given a declaration of a procedure, the instructions it executes never change. 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 if a call to foo() is attempted.

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

/* Procedure named fact, with one argument, n. Only works with Ints */
proc fact(n : Int) : Int
{
    /* Here come the instructions! */
    if (n > 1) {
        var next : Int = 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. In this case, the name must be omitted. For example, the following statement assigns an unnamed procedure to a variable:

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

By default, procedures have no return type, and may return values of any type:

    proc het(t)
    {
        if (t == String) return "string";
        else if (t == Int) return 1;
        else return nil;
    }

    het(String)
string

    het(Int)
1

A return type may be specified. If so, the return value will be converted to the specified return type. If a conversion is not possible, a TypeCheck error will be thrown:

    proc ret(val) : Int
    {
        return val;
    }

    ret(1.4)
1

    ret("two")
Illegal type

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

The argument list is required, even if empty, since OADL won't recognize a statement like this in the body of a procedure:

foo;

Arguments

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

proc foo( a, b, c )
{
}

It is valid to have a procedure which takes NO named arguments; for example:

proc foo()
{
}

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

proc foo( a, b, c );

In OADL, procedure arguments are by-value. Note, however, that for dynamic values such as objects, strings, and arrays, it is the reference to the value that is passed. This means that the content of the composite value can be changed by the procedure. For example:

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

    Name = @ "Sam";
    modif(Name);

    // The first character was modified by the call to modif
    Name
Ham

In contrast, the following program shows that the argument itself can not be modified:

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

    Name = "Sam";
    modif(Name);

    // Name will still be "Sam" since arguments are passed by-value
    Name
Sam

Procedure argument names hide anything with the same name outside the procedure; this can lead to difficult-to-find bugs. In this example, the three numbers "3 4 3" are printed in-order, since the modification of the argument named "num" in procedure foo won't affect the global variable of the same name:

    num = 3

    proc foo(num)
    {
        num = 4;
        say(num, '\n');
    }

    num
3

    foo(3)
4

    // num is still 3, since the global was hidden by foo's proc args
    num
3

The nargs() and arg() intrinsic procedures may be used to facilitate both argument checking as well as variable-length argument lists. Note that these are part of the oadl namespace. The nargs() intrinsic returns the number of arguments passed to the current procedure. The arg() intrinsic returns the value of the nth argument. As an example, the following procedure adds together a list of numbers:

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

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

Similarly, the argvec() intrinsic procedure creates a new List containing the complete list of arguments. This is particularly useful if one needs to handle a variable number of input arguments, and also needs to pass that same variable number of input arguments to another procedure. This can be accomplished by using the #() array call syntax. This form of procedure call takes only one argument, a List of arguments to be extracted and passed as the arguments to the given procedure. Assuming the same addNums() procedure as the previous example, this example prints the list of arguments first then prints their sum:

proc printAddNums()
{
    var a = oadl::argvec();
    "Sum of ", a, " is ", addNums#(a), '\n';
}

The array call must have exactly one argument - the List containing the procedure arguments.

With either named or unnamed arguments, attempts to access an argument that was not actually passed to the procedure results in an ArgCount exception.

Local Variables and Constants

OADL procedures may also have local variables and constants. The most common are temporary locations used as intermediate values in calculations - their value is undefined as soon as the procedure exits. To declare local variables and constants, use the var or const keyword inside a procedure:

proc foo()
{
    const answer = 42;
    var a;
}

Local variables have no defined value until they're initialized. As a convenience, a local variable may be initialized with an expression, thus:

proc foo(a,i)
{
    var b = a[i];
}

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. The compiler typically produces a warning if a local variable name hides an outer declaration.

Local variables remain visible until the end of the block in which they were defined; for example, the following program is illegal since the variable b does not exist after the closing brace of the if statement:

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

Local constants and 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++) {
        say(i, '\n');
    }
}

OADL procedures may also have static local variables. The contents of these variables persist between procedure invocations. They are declared using the static keyword:

proc foo(n)
{
    static prevN = 0;
    say("Delta = ", n - prevN, "\n");
    prevN = n;
}

Variable Assignments

To change the value of a variable, an assignment statement must be executed. There are several kinds of assignment statements. First, simple assignment is of the form:

variable = value;

The variable may be a global variable, a local variable, an argument, or a public or private 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, strings, and dictionaries may be replaced using the syntax:

variable[index_list] = value;

Again, this discards the selected element (or elements, if index_list contains array-valued expressions) and replaces it with the given value. 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:

    Table = @@ { "Mary", "Sam" }
    Table[1][0] = 'P'
    Table
Mary Pam

See the discussion in the Expressions chapter about the @ and @@ operators.

When dynamic objects (classes, objects, strings, arrays, and dictionaries) are used as the right-hand-side of a scalar 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 dynamic object. For example:

    Str1 = @ "Fred"
    Str2 = Str1
    Str2[0] = 'D'
    // The contents of Str1 were modified since Str2 and Str1
    // refer to the same string
    Str1
Dred

The exception to this rule is when an inline array or list is assigned. Inline arrays are created at runtime and are remade anew every time the statement containing the inline array is executed. For example:

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

The first element in each line is 1, since it actually a reference to a new copy of the list {1, 2, 3} that is placed in the variable val.

Finally, public variables in other objects may be assigned to using the "dot" operator . or the with statement. See the Classes chapter for a full explanation:

    class foo {
        public var ack = 3;
    }

    foo bar()

    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, a program might increment a variable by one by stating i = i + 1;. It is equivalent to state: i += 1; or even i++;.

This is very useful if the left hand side has complicated addressing going on:

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

Shortcut assignments generally execute more efficiently than the equivalent longhand assignment statement. Additionally, portions of the left-hand side that have side effects are only executed once with a shortcut assignment:

    proc foo() {"Foo!\n"; return 0;}
    a = [1,2,3]
    a[foo()] = a[foo()] + 10
Foo!
Foo!
    a[foo()] += 10
Foo!

The ++ and -- shortcut assignments do not accept a right-hand side - it is implicitly assumed to be 1. Unlike C and C++, there is no pre-increment or pre-decrement equivalent, nor can these shortcut assignments be used inside expressions; they are merely shorthand for += 1 and -= 1, respectively.

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 different value, execute the return statement with the desired return value:

return value

The value can be any expression (even nil). The procedure is immediately exited, and the calling context resumes execution. As discussed above, if the procedure has a specified return type, the return value will be converted to that type if possible. If not possible, a TypeCheck exception will be thrown.

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. See the list of logically FALSE values in the Expressions chapter for the kind of zero values which are considered false. 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 }. 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.

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 }

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

case exprlist : statements

or of the form:

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).

Arrays and strings, may be freely used in switch statements, and operate as might be expected:

    a = "Hello"
    switch (a) {
    case "World" : "Not reached\n";
    case "Hello" : "Hello, world!\n";
    }
Hello, world!

    a = [4,5,6];
    switch (a) {
    case [1,2,3] : "123\n";
    case [4,5,6] : "456\n";
    }
456

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

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

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

Match Statement

OADL includes a powerful string pattern matching statement. The syntax is very similar to that of the switch statement:

match (expr) { cases }

The expr is any (string-valued) expression and cases are syntactically the same as switch-statement cases. The cases themselves must be strings which contain a Perl-compatible regular expression (see http://perldoc.perl.org/perlre.html for a detailed description of Perl-compatible regular expression syntax).

Multiple cases might contain a pattern which matches the expr. The statements which go with the first matching pattern in the match statement are the ones which will be executed.

Inside each pattern case, there are special variables which may be consulted to extract information from the match. These are the match count ?# and the match substrings ?N (where N is a positive integer).

For example:

    s ="Did we win?"

    match (s) {
    case "(w.n)" : "", ?1, "!\n";
    case "win" :   "We should not be here!\n";
    default :      "No match.\n";
    }
win!

The example printed "win!" since the first match will be the one executed. The implicit print statement will print the word "win" since the ?1 match substring matches the first substring matched in the pattern (which happens to be "win").

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 sense 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

The break statement is usually contained in an if statement:

if (cond) break;

To skip the rest of the instructions in a loop, but go on with subsequent iterations, use the continue statement:

continue

Again, this is usually contained in an if statement:

if (cond) continue;

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. The incr statements will be executed if a continue is executed.

Do/While Statement

OADL provides the do/while statement to provide 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 allowed inside the do statement. A do/while loop always executes at least one time since the condition is evaluated after all of the statements.

Forall Statement

The forall statement allows an OADL programmer to iterate over all of the elements of an array, dictionary, or object. The syntax is very simple:

forall (expr) statement

However, further explanation of the expr is required. The expr must be either a simple indexing expression or an object public reference expression with a name enclosed in parentheses. The array indexes or public reference target are variable names that will be defined inside the forall statement. Here are a couple of examples:

    arr=[1,2,3]
    forall (arr[i]) {
        "arr[",i,"] = ", arr[i], '\n';
    }
arr[0] = 1
arr[1] = 2
arr[2] = 3

    class cls {public var a = 3, b = 4;}
    cls obj()
    forall (obj.(i)) {
        "obj.", i, " = ", obj.(i), '\n';
    }
obj.parent = cls
obj.a = 3
obj.b = 4

The loop will be executed for each element in the target array/dict/object. At each iteration, the index variable will be set to the next valid index. An unused slot in a dictionary will have both a key and a value of nil.

The order of iteration of the elements of objects and dictionaries is not deterministic (it may vary from compile-to-compile, and may even vary depending on the contents of the dictionary).

Assert Statement

OADL provides an assert statement that will throw the AssertCheck exception if the given expression does not evaluate to a logically-TRUE value:

assert expression

With Statement

In keeping with OADL's focus on simple management of object instances and their public data, OADL provides a with statement that allows the programmer to dynamically redefine public variables in an object instance:

with (obj) { assignments }

The obj must be an expression which evaluates to an object. The assignments are a list of items of the form:

name = expr

Each name must be the name of a public variable. There is no restriction on the type of each expr. These assignments are not separated by semicolons - this syntax echoes that of object creation. The completion operator {} will be called after all the public variable assignments are executed. Example:

// Same as:
//     obj.foo = 3;
//     obj.bar = 4;
//     obj.`{}(true, public::foo, public::bar);
with (obj) {
    foo = 3
    bar = 4
}

Implicit Print Statement

Since text output is such a frequent operation in console-oriented programs, OADL offers a short-cut print statement that eliminates the need to explicitly call the say external procedure. Any statement that begins with a string constant is considered an implicit print statement. Multiple items can be included in a print statement by separating them with commas:

// Same as say("Hello ", "world!\n");
"Hello ", "world!\n";

Exception Handling

OADL implements exception handling similarly to other languages such as Java and C++, with try / catch statements:

try statement catch (names) { statements }

OADL also implements throw statements:

throw expression;

Unlike those languages, there can only be one catch clause (since OADL variables are untyped). There are several predefined exceptions that OADL uses internally; these are:

Exception Meaning
TypeCheck An attempt was made to use an illegal type (for example, arithmetic on a string
RangeCheck An argument is out of range (for example, divide by zero)
ArgCheck An incorrect argument was specified (for example, with the intrinsic arg()
AccessCheck An attempt was made to assign a value to an element of a constant array, string, or class.
AssertCheck An assertion failed (this can also happen if hijinx have been performed on class method values)
StackCheck An internal stack overflow or underflow occurred
ExternCheck An attempt was made to use an undefined extern
ShapeCheck An attempt was made to use incompatible array shapes
InterruptCheck An interrupt occurred (for example, the user typed <ctrl>+C on the console)
FormatCheck At runtime, an invalid format string was used
UnboundedCheck An attempt was made to expand an unbounded iterator
EndOfFile An attempt was made read past the end of a File
RegexCheck At runtime, an invalid regular expression used
IoCheck An I/O error (other than end-of-file) occurred
MatchCheck An attempt was made to use match arguments outside of a match statement
ProcCheck An attempt was made to access an undefined procedure
ExceptCheck An unrecognized exception was thrown (typically an internal-only error)
NameCheck An attempt was made to convert an undefined name
RedefinedCheck An attempt was made to redefine an existing class
UTF8Check An illegal UTF-8 byte was read from a text-mode input file

Either one argument or three arguments must be named in the catch statement. If one argument is named in the catch statement, it will contain the thrown exception. If three arguments are named in the catch statement, the first argument is the thrown statement, the second argument is the OADL source file name from which the exception was thrown, and the third argument is the OADL source line number from which the exception was thrown. The file name and line number may not be valid if the OADL program was not compiled with debugging information.

Here is an example that shows catching the RangeCheck thrown by a divide-by-zero. This example also shows how exceptions may be passed back up the stack of exception handlers by themselves throwing an exception.

var i = 0, j;
try {
    j = 1 / i;
}
catch (n) {
    if (n == oadl::RangeCheck) {
        "Caught divide-by-zero\n";
    }
    else {
        throw n;
    }
}

Exceptions need not be of type Exception; any type of expression may be thrown. For example:

    try {
        throw "baseball";
        "Should not get here\n";
    }
    catch(n) {
        "Caught ", n, "\n";
    }
Caught baseball

The break, continue, and return statements execute as expected inside a catch clause:

    for (var i = 0; i < 3; i++) {
        try {
            assert i != 1;
        }
        catch() {
            continue;
        }
        "i = ", i, '\n';
    }
i = 0
i = 2

If a thrown exception is not caught, OADL will print a helpful error message and either continue (if the OADL desk calculator is being used) or exit (if a standalone OADL program is being run):

    throw oadl::TypeCheck
Illegal type
    throw "baseball"
Unhandled exception

Procedure Statement Syntax

The following is the complete syntax of statements allowed inside a procedure.

proc_body       : '{' stmts '}'
                ;

stmts           : /* NOTHING */
                | stmt stmts
                ;

stmt            : assign ';'            | call ';'
                | ifstmt                | whilestmt
                | dostmt ';'            | forstmt
                | forallstmt            | switchstmt
                | matchstmt             | withstmt
                | returnstmt ';'        | assertstmt ';'
                | trystmt               | throwstmt ';'
                | breakstmt ';'         | continuestmt ';'
                | var_decl              | const_decl
                | using                 | printstmt ';'
                ;

assign          : lhs '=' expr          | lhs '+=' expr
                | lhs '-=' expr         | lhs '*=' expr
                | lhs '/=' expr         | lhs '%=' expr
                | lhs '&=' expr         | lhs '|=' expr
                | lhs '^=' expr         | lhs '<<=' expr
                | lhs '>>=' expr        | lhs '++'
                | lhs '--'
                ;

call            : lhs '(' exprs ')'
                | lhs '#(' expr ')'
                ;

lhs             : qual_name
                | lhs '.' IDENTIFIER
                | lhs '.' '(' expr ')'
                | lhs '[' indices ']'
                | lhs '#[' expr ']'
                | '(' lhs ')'
                ;

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

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

dostmt          : 'do' body 'while' '(' expr ')'
                ;

forstmt         : 'for' '(' alst ';' limit ';' alst ')' body
                ;

forallstmt      : 'forall' '(' expr ')' body
                ;

alst            : /* NOTHING */
                | assign_list
                ;

assign_list     : one_assign
                | assign_list ',' one_assign
                ;

one_assign      : assign
                | lhs
                | 'var' IDENTIFIER '=' expr
                | 'var' IDENTIFIER ':' type '=' expr
                | 'static' IDENTIFIER '=' expr
                | 'static' IDENTIFIER ':' type '=' expr
                ;

limit           : /* NOTHING */
                | expr
                ;

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

matchstmt       : 'match' '(' expr ')' '{' cases '}'
                ;

cases           : /* NOTHING */
                | cases onecase
                ;

onecase         : 'case' exprlist ':' stmts
                | 'default' ':' stmts
                ;

returnstmt      : 'return' expr
                | 'return'
                ;

assertstmt      : 'assert' expr
                ;

withstmt        : 'with' '(' expr ')' '{' assignments '}'
                ;

assignments     : /* NOTHING */
                | assignments IDENTIFIER '=' expr
                ;

trystmt         : 'try' body 'catch' '(' names ')' proc_body
                ;

throwstmt       : 'throw' expr
                ;

breakstmt       : 'break'
                ;

continuestmt    : 'continue'
                ;

printstmt       : STRING
                | STRING ',' exprs
                | LSTRING
                | LSTRING ',' exprs
                ;

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

The expr and exprs productions are defined in the Expressions chapter.

Back to Expressions

Continue to Intrinsic Procedures

Return to Introduction