At the outer global scope, an OADL programs may contain any or all of the following:
These elements may be combined in any order. Not all are
required; in fact, a procedural entry point named
main
is not even required. Any of these elements
may be enclosed in a namespace declaration for better organization of
symbols.
A constant declaration associates a name with a constant expression. The syntax for a constant declaration is:
const_decl : 'const' consts ';' ; consts : name_init | consts ',' name_init ; name_init : IDENTIFIER '=' expr ;
where expr is defined in the Expressions chapter. Most expressions are valid constant initializers; these include scalar constants such as numbers; string constants; and even array and procedure constants. Dynamic values, such as variable references, procedure calls, and object references, are not allowed in constant expressions.
Here are some example constant declarations:
const pi = 3.141592653589, euler = 2.7182; const three = '3'; const name = "Mary Smith"; const CardNames = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
OADL predefines several constants for convenient use by the
programmer. These constants include the floating point
Type
constants:
Float |
Half |
Double |
...the integral Type
constants:
Int |
Uint |
Byte |
Ubyte |
Short |
Ushort |
Long |
Ulong |
...the array Type
constants:
PackInt |
PackUint |
PackLong |
PackUlong |
PackShort |
PackUshort |
PackByte |
PackUbyte |
PackChar |
PackWideChar |
String |
WideString |
PackFloat |
PackDouble |
PackHalf |
PackBool |
List |
Array |
...various other Type
constants:
Object |
Dict |
Proc |
Extern |
Public |
Pointer |
Exception |
WideChar |
Char |
Null |
Class |
Type |
Bool |
ArrayType |
Array[*] |
Enclosure |
File |
...the predefined exceptions (found in namespace
oadl
):
TypeCheck |
RangeCheck |
ArgCheck |
AccessCheck |
AssertCheck |
StackCheck |
ExternCheck |
ShapeCheck |
InterruptCheck |
FormatCheck |
UnboundedCheck |
EndOfFile |
RegexCheck |
IoCheck |
MatchCheck |
ProcCheck |
ExceptCheck |
NameCheck |
RedefinedCheck |
UTF8Check |
...system configuration constants (found in namespace
oadl
):
JMP_SIZE |
MAXINT |
MAX_RANK |
...various constants for use by OADL input/output routines
(found in namespace io
):
EOF |
ErrOut |
FMT_ADD_BLANK |
FMT_ADD_PLUS |
FMT_ADD_TYPE |
FMT_ALIGN_ROWS |
FMT_CENTER |
FMT_IMMED_ROWS |
FMT_INTERNAT |
FMT_LEFT |
FMT_NO_BOX_ARR |
FMT_THOUSANDS |
FMT_UNSIGNED |
FMT_UPPER_CASE |
Input |
Output |
SEEK_CUR |
SEEK_END |
SEEK_SET |
...and the Null
, Bool
,
and Object
constants:
nil |
true |
false |
self |
Elements of constant arrays and strings may not be reassigned.
For example, the following code with throw a runtime
AccessCheck
error:
const str = "string"; /* ... */ str[0] = 'f';
Variable declarations consist of the var
keyword followed by one or more variable names. Variables may
optionally have an initializer associated with them. Variables
may also optionally have a type specifier associated with
them.
An initializer is an expression (see the Expressions chapter). If no initializer
is given, the variable is initialized to nil
.
Note that only procedure-local variables can be initialized to
non-constant expressions; all other variables may only be initialized
to constant expressions that can be evaluated at compile time.
A type specifier specifies that the given variable may only
hold values of a given type. If no type specifier is given, then a
variable may hold values of any type. The type specifier consists of
a colon followed by an OADL type (Float
,
Int
, Array
,
PackFloat[3]
, etc.). If a type specifier is
given, assignments to the variable will be type-converted to that
type before the value is stored. If type conversion is not possible,
a TypeCheck
exception will be thrown. See the
chapter on OADL Types for more information
on type specifiers.
The syntax of a variable declaration is:
var_decl : 'var' vars ';' | 'static' vars ';' ; vars : var | vars ',' var ; var : qual_name | qual_name ':' type | qual_name '=' expr | qual_name ':' type '=' expr ;
Variables of any scope (except arguments) may be initialized. The syntax is very similar to named constant declarations. For example:
var Weight = 82, Height : Int = 183;
Just as with constant declarations, the initializer may be an expression. Procedure-local variables may be initialized by any expression; all other variables must be initialized by constant expressions.
Variables can also be initialized to an unnamed procedure using the same syntax as constants:
var name = proc( args ) { statements };
The value of the unnamed procedure may be determined inside the
body of the procedure by using the syntactic convention (
proc )
, thus:
var fact = proc(n) { return (n < 2) ? 1 : n*(proc)(n-1); };
This is especially useful given the fact that unnamed lambda procedures do not have access to local variables of the enclosing procedures (see Identifier Scope, below, for more information).
Procedure local variables may be declared using the
static
keyword. If so, they are placed in the
global variable area and retain their values from call to call.
Otherwise, they are placed on the stack and have a lifetime only
concurrent with the current execution instance of the procedure in
which they are declared. For example:
proc stat() { static entered = false; "", entered ? "Subsequent call" : "First call", '\n'; entered = true; } stat() First call stat() Subsequent call
Note that, in OADL desk calculator mode, the
var
keyword may be omitted:
a = 3 a 3
Constant names, variable names, class names, object names, public names, and procedure names have a scope - a context in which they are valid. There are 5 possible identifier scopes in OADL:
Scope | Identifiers allowed |
---|---|
Procedure block local | Constants and variables |
Procedure arguments | Variables |
Class | Constants, variables, and procedures |
Global | Constants, variables, procedures, classes, and named objects |
Public | All public property names |
The scopes are given in priority order. Identifiers are first
looked for in the nested procedure block scopes, then in the
procedure argument list, then in the enclosing class declaration, and
finally in the global scope. The public scope is special; names
immediately to the right of a .
operator are
looked for in the public scope, as are names explicitly looked up via
the public::
name syntax. The names of
public and protected variables of class definitions are automatically
placed in the public namespace.
Unlake Javascript, nested procedures have no access to names in the enclosing procedure's scope. This allows for a much simpler calling convention. For example, the following procedure will produce a compile-time error:
proc foo() { var a = 1; var b = proc() {"a is: ", a, '\n';} }
External procedures are system-dependent routines implemented by the OADL runtime or by OADL extension libraries. They are declared in an OADL program via the following syntax:
extern_decl : 'extern' qual_names ';' ;
An external procedure can be defined as the default handler for non-object method calls via the following syntax:
default_public : 'default' 'public' defpubs ';' ; defpubs : IDENTIFIER '=' qual_name | defpubs ',' IDENTIFIER '=' qual_name ;
Default handlers may not be redefined:
#include "libstd" 0.->sin() 0. default public cos = m ath::sin Default public cos already defined
See the External Procedures chapter for further discussion.
A procedure declaration either declares or defines a global procedure. The syntax for a procedure declaration is:
proc_decl : 'proc' qual_name '(' args ')' proc_body | 'proc' qual_name '(' ')' proc_body | 'proc' STRING qual_name '(' args ')' proc_body | 'proc' STRING qual_name '(' ')' proc_body | 'proc' qual_names ';' ; args : /* NOTHING */ | arg | args ',' arg ; arg : IDENTIFIER | IDENTIFIER ':' type ; proc_body : '{' statements '}' | ':' type '{' statements '}' ;
where qual_names is the same as in external procedure
declarations, above. If no proc_body is provided, the
statement merely declares the procedure but does not define the
instructions. This is especially useful when linking multiple
modules; the actual implementation of the procedure need not be
exposed in a header file to be included via an
#include
statement.
The STRING is a hint to OADL about attributes of the
procedure. Currently, the only supported attribute is the
indexable attribute, "[]"
, which
tells OADL that the byte code stream of the procedure may be examined
by OADL programs. Each byte of the procedure is accessed by the OADL
index operator, and the number of bytes in the procedure may be found
by the OADL length
intrinsic. Indexable
procedures may be used in foreach
expressions and
forall
statements. Attempting to access the byte
code stream of non-indexable procedures will result in an
AccessCheck
exception.
For example:
proc "[]" foo () {return nil;} foo.length() 19 foo[0] 168
The type specifies a type for an argument of the procedure,
or return type for the procedure. If no return type specifier is
given, then the procedure may return values of any type. See the
section on OADL Types for more information
on type descriptors. If a type descriptor is used, return values will
be type-converted to that type before the procedure returns. If type
conversion is not possible, a TypeCheck
exception
to be thrown.
The statements production may be found in the Procedures chapter.
The following are examples of valid procedure declarations:
proc a, b, c; proc fact(n : Int) { if (n > 1) { return n * fact(n-1); } else { return 1; } }
A class declaration either declares or defines a class. A class is an abstract data type that includes data storage along with operations on the data. The syntax for a class declaration is:
class_decl : 'class' qual_name '{' props '}' | 'class' qual_name '(' names ')' '{' props '}' | 'class' qual_names ';' ; props : /* NOTHING */ | props prop ; prop : 'public' oneprop | 'protected' oneprop | 'operator' oper_decl | oneprop ; oneprop : var_decl | const_decl | proc_decl ; oper_decl : operator no_name_proc ; operator : '|' | '^' | '&' | '<' | '>' | '+' | '-' | '*' | '/' | '%' | '~' | '!' | '##' | '==' | '!=' | '<=' | '>=' | '<<' | '>>' | '@' | '=>' | '**' | '++' | '--' | '#=' | '\|' | '\^' | '\&' | '\<' | '\>' | '\+' | '\-' | '\*' | '\/' | '\%' | '\==' | '\#=' | '\!=' | '\<=' | '\>=' | '\<<' | '\>>' | '\=>' | '\~=' | '\**' | '!-' | '{' '}' | '[' ']' | '[' '=' ']' | '(' ')' | '#[' ']' | '#[' '=' ']' ; no_name_proc : '(' args ')' '{' statements '}' | '(' ')' '{' statements '}' ;
The args and statements productions may be found in the Procedures chapter. Further discussion of the semantics of classes and objects may be found in the Classes and Objects chapters.
The following are examples of valid class declarations:
class a, b, c; class foo { public proc bar() {say("bar");} } class foobar(foo) { public proc bletch {say("bletch");} } class complex { protected var real, imag; public proc create(r, i) { real = r; imag = i; } operator + (rhs) { var res; if (rhs ?= complex) { res = new complex(real + rhs.real, imag + rhs.imag); } else { res = new complex(real + rhs, imag); } return res; } /*...*/ }
An object declaration declares or defines an object. An object is an instance of a class. The syntax of an object declaration is:
/* Forward declarations and object definitions with no property * assignments */ obj_decl_list : obj_decl_noprop | obj_decl ',' obj_decl_noprop ; obj_decl_noprop : qual_name qual_name | qual_name qual_name '(' exprs ')' ; /* Object definitions with property assignments */ obj_decl_props : qual_name qual_name obj_proplist | qual_name qual_name '(' exprs ')' obj_proplist | '...' qual_name obj_proplist ; obj_proplist : '{' obj_props '}' | '{' obj_props '...' '}' ; obj_props : /* NOTHING */ | obj_props prop_init ; prop_init : IDENTIFIER '=' expr ; exprs : /* NOTHING */ | exprlist ; exprlist : expr | exprlist T_COMMA expr ;
Further discussion of the semantics of classes and objects may be found in the Classes and Objects chapters.
An example object declaration is:
box mybox { length = 10 width = 5 height = 4 }
When creating an OADL module to be linked in by other programs, it is very useful to be able to declare classes and their public interfaces in a header file without specifying their full implementation. The public attribute declaration syntax declares an interface:
public_decl : 'public' names ';' ;
An example using public attribute definitions is:
class box; public length, width, height, draw, resize, setcolor;
The box class is (presumably) defined in an OADL source file which is to be separately compiled and linked in with the main OADL program.
Global identifiers may be grouped into namespaces for
better management of identifier conflicts. By default, there are four
namespaces that OADL defines: the anonymous global namespace, the
public
namespace, the io
namespace, and the oadl
namespace. The public
namespace contains all names defined via public
statements. All other global names are placed by default in the
anonymous global namespace, unless a new named namespace is being
constructed. This is similar to Java and C++ namespaces in syntax and
concept. An OADL program may define a new namespace via the
namespace
declaration:
namespace_decl : 'namespace' name '{' items '}' qual_name : name | name '::' name | '::' name ; qual_names : qual_name | qual_names ',' qual_name ;
The items in a namespace may refer to each other without
qualification. Items outside the name space may only refer to items
inside the namespace via the ::
syntax or with
the using
statement:
using_stmt : 'using' one_using ';' | using_statmt ',' one_using ';' ; one_using : 'namespace' name | 'extern' string | name '::' name ;
If the program includes the using namespace
statement, all of the symbols in the namespace may be used without
qualification. Additionally, the program may selectively include only
certain symbols via the using
ns
::
name syntax:
using
statements may be placed at the global
scope or the procedure block scope, but not the procedure argument
scope or class scope. If a using
statement is
placed at the procedure block scope, the included names are only
accessible until the end of the enclosing block.
Here is an example of a using
statement that
imports a single symbol into the global namespace:
#include "libstd.oah" using math::cos;
The public namespace is implicitly used, and OADL will
disambiguate names from the public namespace and the current
namespace scope based on context - names immediately to the right of
the .
operator are assumed to be from the public
namespace. To prevent this disambiguation, place parenthesis around
the name to the right of the .
operator:
var create = public::destroy; obj.(create)(); // actually calls the "destroy" method
Names from the anonymous global namespace may be accessed via the
::
name qualified name format:
var n = 1; proc main() { var n = 2; // Should print "n = 2; ::n = 1" "n = ", n, "; ::n = ", ::n, '\n'; }
The using extern
syntax is used to interface
an OADL program with an external procedure library; see the chapter
on External Procedures for more
information.