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;
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.
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; }
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.
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.
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.
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.
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").
The while
statement is the simplest loop
iterator in OADL. The syntax of the while statement is:
while( condition ) statement
Just like if
/els
e, 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;
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.
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.
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).
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
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 }
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";
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
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.