Ox Syntax Reference

Chapter contents:

Lexical conventions
Comment

Identifiers
Keywords
Constants
Integer constants
Character constants
Double constants
Matrix constants
Null constants
String constants
Raw string constants
Array constants

Objects
Types
Type conversion

Lvalue
Scope

External declarations
Enumerations
Storage class specifiers
Type qualifiers
External variable declarations
Functions
Function declarations
Function definitions
Returning values
Default values for function parameters
Variable length parameter list

Classes
Member function definitions
Constructor and destructor functions
public and protected members, structs
The this reference and member scope
Static members
Derived classes
Virtual functions

Namespace
Statements
Selection statements
Switch statements
Iteration statements
Jump statements
Declaration statements
try-catch block and throw
Closed block
Parallel programming
Canonical for and foreach loops
Parallel for and foreach loops

Expressions
Primary expressions
Multiple assignment and multiple returns
Lambda functions

Postfix expressions
Member reference
Function calls
Spread operator
Explicit type conversion
Indexing vector and array types
String indexing of array types
Postfix incrementation
Transpose

Unary expressions
Prefix incrementation
Unary minus and plus
Logical negation
Address operator
New and delete

Power expressions
Multiplicative expressions
Additive expressions
Concatenation expressions
Relational expressions
Equality expressions
Logical dot-AND expressions
Logical-AND expressions
Logical dot-OR expressions
Logical-OR expressions
Conditional expression
Assignment expressions
Comma expression
Constant expressions

File inclusion and preprocessing
Using folder names in Ox
Search path in Ox
File inclusion
Import of modules
Conditional compilation
Pragmas

Some differences with C and C++

Tables:

Escape sequences
Operator precedence
Result from dot-style binary operators
Result from relational operators
Result from operators involving an empty matrix as argument
Result from relational operators involving missing values


Lexical conventions

Comment

Anything between /* and */ is considered comment. This comment can be nested (unlike C and C++). Everything following // up to the end of the line is also comment, but is ignored inside /* ... */ type comment. So nested comment is possible:

one = cons + 1; // comment /* two = cons + 1; // comment */

A special form of comment adds an extra * at the start: \Code\begin{verbatim}

@returns string with package name */ CATS::GetPackageName() { return "CATS"; }

This is used by OxDoc (see ox/doc/packages/) to create html files with documentation.

Identifiers

Identifiers are made up of letters and digits. The first character must be a letter. Underscores (_) count as a letter. The maximum length of an identifier is 60 characters, additional characters are ignored.

Keywords

The following keywords are reserved:

array default goto private switch break delete if protected switch_single case do inline public this catch double int return throw char else matrix serial try class enum namespace short virtual const extern new static while continue for operator string decl foreach parallel struct

Constants

Integer constants

A sequence of digits is an integer constant. A hexadecimal constant is a sequence of digits and the letters A to F or a to f, prefixed by 0x or 0X.

Character constants

Character constants are interpreted as an integer constant. A character constant is an integer constant consisting of a single character enclosed in single quotes (e.g. 'a' and '0') or an escape sequence enclosed in single quotes.

Table syn.1: Escape-sequence

\" double quote \' single quote \0 null character \\ backslash \a alert (bel) \b backspace \f formfeed \n newline \r carriage return \t horizontal tab \v vertical tab \xhh hexadecimal number hh


So '\n' is the integer constant corresponding to the newline character.

Double constants

A double constant consists of an integer part, a decimal point, a fraction part, an e, E, d or D and an optionally signed integer exponent. Either the integer or the fraction part may be missing (not both); either the decimal point or the full exponent may be missing (not both). Special values that are recognized are:

.NaN
Not a Number,
.Inf
infinity, which can be signed,
.last
to index the last element.
.NaN propagates in expression, eo, e.g. 1+.NaN is also .NaN. They can be detected e.g. (x == .NaN) or using isnan, ismissing.

A hexadecimal double constant is written as 0x.hhhhhhhhhhhhhhhh. The format used is an 8 byte IEEE real. The hexadecimal string is written with the most significant byte first (the sign and exponent are on the left). If any hexadecimal digits are missing, the string is left padded with 0's.

Double constants in an external declaration may use a dot to represent a missing values. This sets the variable to .NaN.

Null constants

An unitialized variable has the untyped value .Null. Using an unitialized variable in an expression results in a run-time error.

When an array is created using new, its elements will have value .Null. In that case, .Null can be used in an equality comparsion, e.g. (a[1] == .Null) or detected using ismissing.

Matrix constants

A matrix constant lists within < and > the elements of the matrix, row by row. Each row is delimited by a semicolon, successive elements in a row are separated by a comma. For example:

< > < 00, 01, 02; 10, 11, 12 > < 0.0, 0.1, 0.2 > < 1100 >

which are respectively an empty matrix, a 2 by 3 matrix, a 1 by 3 matrix and a 1 by 1 matrix, the first constant is:

00 01 02 10 11 12

The comma is optional, so the first matrix constant may be written as (the semicolon is still required):

< 00 01 02; 10 11 12 >

The index of each row is one higher than the previous row. Within each row, the column index of an element is one higher than that created with the previous element in the same row.

An integer range may be specified, e.g. 2:5 corresponds to 2,3,4,5. The range may decrease, so that 5.3:2.8 corresponds to 5.3,4.3,3.3.

A stepsize may be specified, as in 2:[2]8, which gives 2,4,6,8.

A specific element in the matrix can be set. This overrides the location implicit in the position of the element in the matrix constant. Note that the top left element is [0][0], the second element in the first row [0][1], etc.

A number of identical elements can be specified, e.g. [3]*0 corresponds to 0,0,0. Unspecified elements are set to zero.

As an example involving all types, consider:

< [4]*1,2; 10,11,14-2; 1:4; [3][4]=99,2; 8:[-3]2 >

which corresponds to:

1 1 1 1 2 0 10 11 12 0 0 0 1 2 3 4 0 0 0 0 0 0 99 2 8 5 2 0 0 0

Missing values in a matrix constant could be represented with a dot or .NaN, which represents NaN (Not a Number), e.g.:

< .,2,3; 4,.,6 >

Similarly, .Inf represents infinity. Further examples are given in external declarations.

String constants

A string constant is a text enclosed in double quotes, for example: "tailor". Adjacent string constants are concatenated. A null character is always appended to indicate the end of a string. The maximum length of a string constant is 1024 characters. Escape sequences can be used to represent special characters.

Raw string constants

A raw string constant is a text enclosed in backticks. They differ from a string constant (enclosed in double quotes) in several ways:

The maximum length of a raw string constant is 2048 characters. A raw string constant is useful to embed code of another language: decl shtml = ` <div class="xyz"> <a href="https://oxrun.dev">Try Ox</a> </div> `;

Array constants

An array constant is a list of constants in braces, separated by a comma. This is a recursive definition, because the constant can itself be an array constant. The terminating level consists of non-array constants. Each level of array constants creates an array of references. For example:

{ "tinker", "tailor", "soldier" } {{ "tinker", "tailor"}, {"soldier"} }

Objects

Types

Variables in Ox are implicitly typed, and can change type during their lifetime. The life of a variable corresponds to the level of its declaration. Its scope is the section of the program in which it can be seen. Scope and life do not have to coincide.

There are three basic types and several derived types.

In addition there is: reference, file, and blob.

Type conversion

When a double is converted to an int, the fractional part is discarded; if the resulting value cannot be represented, the behaviour is undefined. When an int is converted to a double, the fractional part is discarded. For example, conversion to int of 1.3 and 1.7 will be 1 on both occasions.

A single element of a string (a character) is of type int. An int or double can be assigned to a string element, which first results in conversion to int, and then to a single byte character.

Also see explicit type conversion.

Lvalue

An lvalue is an object to which an assignment can be made.

Scope

Variables declared at the start of a statement block have scope and life restricted to the block. These variables are called automatic: they are created and initialized whenever the block is entered, and removed as soon as the block is exited. Variables declared outside any statement block have global scope and life; these are called static. Note that Ox assignment of arithmetic types and string type implies copying over the contents from the right-hand side to the left-hand side. Automatic variables of any type can be assigned to variables with broader scope.

External declarations

An Ox program consists of a sequence of external declarations. These either reserve storage for an object, or serve to inform of the existence of objects created elsewhere. Each program must define one function called main, where execution of the program will start. The return value from main (if any) is returned to the console window.

Enumerations

An enumeration defines a list of integer constants. By default, the first member will have value 0, and each successive member will have a value of one plus that of the previous member. The value of a member can be set by assigning it a constant integer value (then, if the next element is not set, it will be one higher than the previously set value).

Enumerator names only exist in the file in which they occur. Enumerations should be placed in header files if they need to be shared between several source files. Enumerators can also be declared withn a class.

Here are some examples with corresponding values:

enum { C_FIRST, C_SECOND, C_THIRD }; // 0,1,2 enum { T_INT, T_DBL=2, T_STR, T_MAT=C_THIRD }; // 0,2,3,2 enum { FLAG0,FLAG1, FLAG2=FLAG1*2, FLAG3=FLAG2*2};//0,1,2,4 enum { T_ERR = 1.0 } ; // error

Storage class specifiers

External variable declarations (i.e. declared outside a function) create global variables: such variables exist while the program runs. The static specifier restricts the scope of the declared object to the remainder of the file. Although it will exist throughout the program's life, it cannot be seen from other files. In classes, the static keyword is used with a different meaning.

The extern specifier informs the remainder of the file that the object can be accessed, although defined (created) in another file. The extern and static specifiers are mutually exclusive. External declarations are most conveniently placed in header files. extern is also used to import a function from a dynamic-link library.

Type qualifiers

A const object can only be initialized once, and not changed thereafter. The use of serial is explained in this section. The const and serial qualifiers are mutually exclusive.

External variable declarations

The static or extern specifier and the const qualifier preceding an external variable declaration list applies to all variables in the list. Each identifier creates space for an object with global lifetime, unless declared extern or const.

A const object must be initialized (unless declared extern) but its value may not be changed thereafter. Unless declared extern, a const object cannot be accessed from other files. If of scalar type, a const can appear in a constant-expression.

At the external level of declarations, as treated here, it is possible to specify a matrix size, and initialize that matrix to zero. If an external variable is created without explicit value and without dimensions, it will default to an int with value 0. Here are some examples:

decl a, b; // default to type int, value 0 enum { AAP, NOOT, MIES, WIM }; const decl ia = NOOT, ib = NOOT + WIM; // type: int const decl ma = < NOOT, AAP; 0, 1 >; // type: matrix const decl aa = {"tinker", "tailor"}; // type: array decl id = ia * (WIM - 1) * MIES + ib; // type: int decl da = ia + 0.; // type: double decl mb = <0:3; 4:7; 8:11>; // type: matrix decl ab = { ma, ma}; // type: array extern decl elsewhere; // defined in other file decl mc[3][3] = 1.5; // 3 x 3 matrix with values 1.5 static serial decl s_md[2][1]; // 3 x 1 matrix of zeros enum { ZUS = id }; // error: id is not const decl ih = id; // error: id is not const decl ia; // error: already defined

Global variables increase the complexity of a program, and are better avoided. If needed, declare them as static; usually wrapping it in a class is an alternative.

Functions

Function declarations

A function declaration communicates the number of parameters (or formal arguments) and their types to another code unit, so that the function can be called correctly from that file. The actual creation of the function is done through a function definition (which at the same time declares the function). A function can be declared many times, but type and number of parameters must always be identical:

test0(); // function takes no parameters test1(const a1); // one const parameter test2(const a2, a3=3);// second pareameter has default value static test3(a1); // cannot be used outside this file extern test4(a1); // function defined outside this file print(a1, ...args); // variable number of parameters test1(a1); // error: previous declaration was different

The second form, using extern string-constant, provides dynamic linking\index{Dynamic linking} of functions residing in a dynamic-link library (which could be written in C, FORTRAN, etc.; creation of dynamic link libraries is platform dependent). In the following example, test5 corresponds to the external function MyCFunc(), located in the dynamic library mydll.

extern "mydll,MyCFunc" test5(a1);

The appropriate extension is appended automatically. The following table lists the defaults that are searched first (thus allowing the folder structure to be shared between platforms):

mydll.dll Windows 64-bit mydll_mac.so MacOS 64-bit (combined for x86 and ARM arhitectures) mydll.so Linux 64-bit

If the DLL is not found, then for backward compatibility, the name with _64 added is tried. When the Ox program is linked, mydll will be automatically loaded, and the function imported.

Function definitions

A function definition specifies the function header and body, and declares the function so that it can be used in the remainder of the file. A function can be declared many times, but defined only once.

The use of const is recommended: parameters declared const can be referenced, but cannot be changed inside the function. If the parameter is a const reference, the reference cannot be changed, but what it references can. The decl keyword is optional in front of a parameter. An empty parameter list indicates that the function takes no parameters at all. The ... indicates a variable number of parameters; it must have the last position in the header, but cannot be the first.

test1(const a1); // declaration of test1 print(a1, ...); // variable number of parameters test2(const a1, a2) // definition of test2 { test1(a2); // call function test1 print(a1, 1, 2, "\n"); // at least one argument test1(a2, 1); // error: wrong number of arguments a2 = 1; // a2 may be changed a1 = 1; // error: a1 is const /* ... */ }

All function arguments are passed by value. This means that a copy of the actual object is made (although Ox will avoid this internally if the argument is not assigned to to make function calls more efficient). For int, double, matrix, string and array types the whole object is copied, and any changes are lost as soon as the function returns.

Objects of a class are accessed through a reference, and that reference is passed by value. However, what is referenced may be changed, and that change will remain in effect after function return.

It is possible to take a reference of a variable with & in a function call. and the function that is called can then change the content of the referenced variable. So passing references allows a function to make a permanent change to a variable, for examples see function calls. A reference can only be taken in a function call, and cannot be assigned to a variable .

Lambda functions are introduced below.

Returning a value

All functions may have a return value, but this return value need not be used by the caller. If a function does not return a value, its actual return value is zero.

The return statement returns a value from the function, and also exits the function. So, when the program flow reaches a return statement, control returns to the caller, without executing the remainder of the function.

The syntax of the return statement is:
return return_value ;
Or, to exit from a function which does not have a return value:
return;

The following example illustrates the use of return:

threes(const r, const c) // definition of threes { return constant(3, r, c); } otherfunc() { println(threes(2, 2)); }

Multiple returns can be implemented through the Multiple assignment statement:

func(const r, const c) { return {zeros(r,c), ones(r,c)}; // array with 2 elements } otherfunc() { decl [x1, x2] = func(3, 3); //element [0] in x1 and [1] in x2 }

Default values for function arguments

Default values for function arguments can be supplied, subject to the following constraints

  1. A default value cannot be replaced by another default.
  2. The value must be within scope when the call is made, so that it can be substituted when compiling.
  3. When a default value is supplied for a parameter, all subsequent parameters must have a default value.

Default values for member calls and functions that are called as a string are injected at run-time. This is possible, because the default values become a property of the function.

The following example illustrates the use of a default value for an argument:

#include <oxstd.oxh> func(arg=<1,1>); // forward declaration main() { func(); // same as func(<1,1>); } func(arg) // definition { println("func arg=", arg); }

Variable length parameter list

A named addition to the final ... creates a local variable that receives the remaining arguments in an array, e.g.:

test(...args) { for (decl i = 0; i < sizeof(args); i++) println("argument ", i + 1, ": ", args[i]); } main() { test("tinker", "tailor", "soldier"); }

which prints:

argument 1: tinker argument 2: tailor argument 3: soldier

The ... must always be the last parameter. The format ...args is convenient shorthand that avoids using the special library function va_arglist():

test(...) { decl args = va_arglist(); for (decl i = 0; i < sizeof(args); i++) println("argument ", i + 1, ": ", args[i]); }

Note that, when used in main, as in main(...args), the call is made to arglist instead to obtain the command line arguments.

Classes

A class is a collection of data objects combined with functions operating on those objects. Access to data members from outside the class is through member functions: only member functions can access data directly (at least, that is the default, see below). So by default, all data members are protected, and all function members public, using C++ parlance.

Consider a simple line class, which supports drawing lines from the current cursor position to the next, and moving the cursor:

class Line // Line is the class name { decl m_x = 0, m_y = 0; // two data members, initialized const decl m_origin; // const data member static decl sm_cLines; // static data member Line(const orig=0); // constructor moveto(const x, const y); // move cursor lineto(const x, const y); // draw line and move cursor static getcLines(); // static function static setcLines(c); // static function public: static const decl M_CONST = 1;// value must be set here enum { M_AA, M_BB = -1}; }; // ; is optional in Ox (unlike C++)

All member names within a class must be unique. A class declaration introduces a type, and can be shared between source files through inclusion in header files. Ox accesses an object through a reference to the object which is created using the new operator. An object is removed from memory using the delete operator (if there is no matching delete, the object will exist until the program terminates). Both new and delete are unary operators.

A member function declaration can specify default values for parameters, subject to the restriction that, when a default value is supplied for a parameter, all subsequent parameters must have a default value. Default values are added to the call at run time.

Data members that are static const must be initialized in the class declaration. All other data members can be initialized in the class declaration or in the constructor function. Unless they are const, they can also be changed in other member functions. If member variables are not explicitly initialized, they default to zero (this is new in Ox 9).

Enumerations of constants can be defined within the class through the enum keyword. Constants defined through enum behave the same as static const decl member variables. In the example above, the public keyword means that M_CONST, M_AA and M_BB can be accessed from outside the class as Line::M_CONST, etc. This is a convenient way to encapsulate the constants.

Member function definitions

A member function provides access to data members of an object. It is defined as its class name, followed by :: and the function name. The function name must have been declared in the class. Member functions cannot be declared outside a class; the class declaration contains the member function declaration. Only a member function can use data members of its own class directly.

Function member definitions cannot specify default parameters: must be specified in the declaration instead (which is usually in a header file).

Here are the definitions of the member functions of class Line:

Line::Line(const orig) { m_x = m_y = orig; // set cursor at the origin m_origin = orig; // only allowed in constructor sm_cLines++; // count number of Line objects } Line::moveto(const x, const y) { m_x = x; m_y = y; println("moved to ", x, " ", y); return this; } Line::lineto(const x, const y) { // draw the line from (x,y) to (ax,ay) ... m_x = x; m_y = y; println("line to ", x, " ", y); return this; }

The new operator creates an object of the specified class, calls the constructor function, and returns a reference to it. A member function is called through a member reference, which is a class object named followed by a dot. For example:

lineobj = new Line(0); // create object and // set cursor to (0,0) lineobj.lineto(10, 10); // draw line to (10, 10) lineobj.Line::lineto(10, 10); // same call lineobj::lineto(10, 10); // error, needs . lineobj.moveto(10,10).lineto(20, 30).lineto(30, 30); delete lineobj; // delete object from memory when done

Since lineobj is of class Line, both calls to lineto are to the same function. The only difference is one of efficiency. Ox has implicit typing, so can only know the class of lineobj at run time. In the second case the class is specified, and the function address can be resolved at compile time.

The last call uses a command chain, which works because both lineto and moveto return the hidden this object.

Starting from Ox 9, objects are reference counted, and there is no need anymore to explicitly delete them. The only situation where this does not work is when objects refer to each other.

Note that objects are always passed by reference, and not copied. The clone function can be used to make a copy of an object.

Constructor and destructor functions

The member function with the same name as the class is called the constructor, and is automatically invoked when creating an object of the class. The constructor function may be absent, but may not be static. A constructor always returns a reference to the object for which it was called and may not specify a return value. Only the constructor function may set const data members. In the Line class, the origin is only set during construction, and not thereafter. However, each Line object has its own origin (unless origin is made static, in which case it is shared between all objects).

The optional destructor function is called after a request to delete an object, and before the object is actually removed. It may be used to clear up any allocated objects inside the object to be deleted. A destructor function has the same name as the class, is prefixed by ~, and may neither take arguments, nor return a value. It does however receive the this reference.

class Line { /* ... */ Line(const orig); // constructor ~Line(); // destructor /* ... */ }; test() { decl lineobj; lineobj = new Line(0);//create object, call constructor delete lineobj; // call destructor, delete object }

public and protected members, structs

All function members are public and data members are protected by default in a class. This means that function members can be called from anywhere by accessing an object, while data members can only be accessed from inside a class or derived class:

class Line { /* ... */ decl m_x; Func(); }; Line::Func() { m_x = 0; // can access data member from inside } test() { decl lineobj = new Line(0); lineobj.Func(); // can access function member lineobj.m_x = 1; // error: cannot access data member }

A struct differs from a class only in that all members are public. So, if in the above example we would have used struct Line, then the last line (lineobj.m_x = 1) would have been allowed.

More fine-grained control is available using the public and protected specifiers: some variables or enumerations can be made accessible, and others not. The following code illustrates:

class Line { /* ... */ public: decl m_x; decl m_y; protected: decl m_z; Func(); }; test() { decl lineobj = new Line(0); lineobj.Func(); // can access function member lineobj.m_y = 1; // OK: m_y is public lineobj.m_z = 1; // error: m_z is protected }

Note, however, that in Ox, the addition of public and protected only applies to variables. Functions remain public.

The this reference and member scope

All non-static member functions receive a hidden argument called this, which points to the object for which the function is called. So the constructor function Line obtains in this a reference to the newly created object. The assignment to m_x and m_y refer to the members of the this object. When accessing a variable in a member function, it is determined first whether the function is a local variable or an argument. Next it is considered as a member of this. If all these fail, it is considered as a global variable. So local variables and arguments hide members, together these hide global variables. The following example shows how the scope resolution operator :: may be used to resolve conflicts:

decl x, y; // global variables extern moveto(x, y); // external function Line::moveto(const x, const y) { ::x = x; // assign arguments to global variables ::y = y; this.m_x = x; // assign arguments to data members this.m_y = y; // this. needed if these were als x and y ::moveto(x, y); // call non-member function moveto(x, y); // error: call to itself will } // cause infinite loop

Static members

There is only one copy of a static member, shared by all objects of a class. A static member may not have the same name as the class it is in.

Derived classes

A class may derive from a previously declared class. A derived class will inherit all members from its base class, and can access these inherited members as its own members. However, if the derived class has members with the same name as members of the base class, the former take precedence. In this way, a class can redefine functionality of its base class. If a function is redefined, the base class name followed by :: may be used to refer to the base class function. Deriving from the Line class:

class Angle : Line // Line is the base class { Angle(); // constructor lineto(const x, const y); // draw dash, move cursor }; Angle::Angle() { Line(); // starts at zero } Angle::lineto(const ax, const ay) { Line::lineto(ax, y); // horizontal line Line::lineto(ax, ay); // vertical line print("is angle to ", ax, " ", ay, "\n"); moveto(ax, ay); }

Angle's constructor just calls the base class constructor, as the body may be read as this->Line();. Note that the base class constructor and destructor functions are not called automatically (unlike in C++). In the new lineto object, Line::lineto is used to make sure that we call the correct function (otherwise it would make a recursive call). For the moveto that is no problem, moveto calls the base function, as it was not redefined in the Angle class. Non-static member functions may be declared as virtual (that is, they can be redefined by a derived class), this is discussed in the next section.

New classes may be derived from a class which is itself derived, but Ox only supports single inheritance: a class can only derive from one other class at a time.

Virtual functions

Virtual functions allow a derived class to supply a new version of the virtual function in the derived class, replacing the version of the base class. When the base class calls the virtual function, it will actually use the function of the derived class. For a virtual function, the call can only be resolved at run time. Then, the object type is known, and the called function is the one first found in the object, when moving from the highest class towards the base class. A virtual function cannot be static.

Namespace

namespace identifier
{    external-declaration
}

A namespace surrounds a section of external declarations, separating it from functions and variables in other namespaces, or from those outside the namespace. If the namespace is called ns, then identifiers inside the namespace are first resolved within that namespace, and then in the unnamed space. From another namespace, access is by prefix ns::. Namespaces in Ox cannot be nested, and unnamed namespaces are unsupported.

foo() { println("foo"); } bar() { println("bar"); } namespace test { bar() { println("test::bar"); } foo() { println("in test::foo"); bar(); // calls test::bar ::bar(); // calls bar } } // end of namespace main() { println("calling ::foo"); foo(); println("calling test::foo"); test::foo(); }

which prints:

calling ::foo in foo calling test::foo in test::foo in test::bar in bar

Statements

The executable part of a program consists of a sequence of statements. Expression statements are expressions or function calls. It can be a do-nothing expression, as in:

for (i = 0; i < 10; i++) ;

A compound statement groups statements together in a block, e.g.:

for (i = 0; i < 10; i++) { a = test(b); b = b + 10; }

A statement can be prefixed by a label as in:

:L001 for (i = 0; i < 10; i++) ;

Labels are the targets of goto statements; labels are local to a function and have a separate name space (which means that variables and labels may have the same name). Note that labels are defined in a non-standard way: the colon is prefixed, rather than suffixed as in C or C++. It is considered bad programming practice to use goto.

Selection statements

The conditional expression in an if statement is evaluated, and, if it evaluates to true, the statement is executed. The following all evaluate to false:

The conditional expression may not be a declaration statement.

Some examples for the if statement:

if (i == 0) i++; // do only if i equals 0 if (i >= 0) i = 1; // do only if i >= 0 else i = 0; // set negative i to 0 if (i == 0) if (k > 0) j = 1; // do only if i != 0 and k > 0 else // this else matches the inner if j = -1; // do only if i != 0 and k <= 0 if (i == 0) { if (k > 0) j = 1; // do only if i != 0 and k > 0 } else // this else matches the outer if j = -1; // do only if i != 0

Each else part matches the closest previous if, but this can be changed by using braces. When coding nested ifs, it is advisable to use braces to make the program more readable and avoid potential mistakes.

Ox 9 allows the conditional expression to be preceded by a declaration

if (decl i = unit(2); i == 0) // prints false println("true!"); else println("false!"); // is shorthand for: { decl i = unit(2); // i is local to the if if (i == 0) println("true!"); else println("false!"); }

Further examples involving matrices are given in equality expressions.

Switch statements

A switch statement is a compact way of writing a sequence of if statements involving the same variable for comparison:

decl i = 1; switch (i) { case 0: println("zero"); break; case 1: println("one"); break; default: println("not zero, not one"); break; }

which prints: "one". There is a sequence of case blocks, and an optional default block, which must be the last. The break statement jumps out of the switch statement. Here, the value of i is compared to each value in turn, until a comparison is true. Then all the statements for that case and all subsequent cases are executed (including the default) until a break is encountered. If no case is true, the default statements are executed. So, once inside a case, we automatically fall through to the next case. The advantage is that several cases can be grouped together:

switch (i) { case 0: println("zero"); break; case 1: case 2: println("one,two"); break; default: println("default"); break; }

printing one,two when i is 1 or 2. The drawback is that is easy to forget the break statements, and get unexpected results. The following code

switch (i) { case 0: println("zero"); case 1: case 2: println("one or two"); default: println("default"); }

will print when i equals zero:

zero one or two default

To emphasize that distinction, and allow for more readable code, Ox also has the switch_single statement. Then, one and only one case (or default) is executed:

switch_single (i) { case 0: println("zero"); case 1: println("one"); case 2: println("two"); case "two": println("two as a string"); default: println("default"); }

In practice switch_single is used more often than switch. From Ox 9, different types can be used in the case statements. This works because string == int is false.

Iteration statements

The while statement excutes the substatement as long as the test expression is nonzero (for a matrix: all elements are nonzero). The test is performed before the substatement is executed.

The do statement excutes the substatement, then repeats this as long as the test expression is nonzero (for a matrix: all elements are nonzero). The test is performed after the substatement is executed. So for the do statement the substatement is executed one or more times, whereas for the while statement this is zero or more times.

The for expression: for (init_expr; test_expr ; increment_expr) statement corresponds to:

{
  init_expr;
  while (test_expr )
  {
  statement
  increment_expr;
  }
}

Note that, when the init_expr is a declaration statement, the declaration is local to the for statement.

The foreach expression is used to loop over all elements in a matrix, array or string. The most simple form:

  foreach (element-identifier in collection-identifier) statement

implements a loop over all elements in the collection. The following restrictions apply to the foreach loop:

The foreach-index-expression: part determines how the loop is performed:

Some examples:

decl x, m = rann(2,2), i, j; // Example 1: print all elements foreach (x in m) println(x); foreach (decl x in m[i][j]) // x,i,j local to loop println("element ", i, ",", j, ": ", x); // Example 2: create a Toeplitz matrix decl c = zeros(10, 10); foreach (x in c[i][j]) c[i][j] = fabs(i - j) + 1; // Example 3: print all strings in an array: decl a = {"aaa", m, "BBB"}, s; foreach (s in a) if (isstring(s)) println(s);

Jump statements

The return statement exits the function; if it is followed by an expression, the value of the expression is returned to the caller, see returning values.

A continue statement may only appear within an iteration statement and causes control to pass to the loop-continuation portion of the smallest enclosing iteration statement.

The use of goto should be kept to a minimum, but could be useful to jump out of a nested loop, jump to the end of a routine or when converting Fortran code. It is always possible to rewrite the code such that no gotos are required.

A break statement may only appear within an iteration statement and terminates the smallest enclosing iteration statement.

Two examples:

for (i = 0; i < 10; i++) { if (test1(i)) continue; test2(); // only done if test1(i) returns 0 } for (i = 0; i < 10; i++) { if (test1(i) == 0) break; // jump out of loop if test1(i) returns 0 test2(); }

Declaration statements

Declarations at the external level were discussed before. Here we treat declaration within a block.

Declaration statements create a `local' variable for further manipulation as long as it stays within scope. The created object is removed as soon as the block in which it was created is exited. Variables or initialized to .Null unless dimensions are set or an asisgnment is made in the declaration statement.

Variables in Ox are implicitly typed, and their type can change during program execution. Locally declared variables must be initialized before they can be used in an expression. Trying to use the value of a variable that has been declared but not yet assigned to results in a run-time error.

It is not possible to specify matrix dimension as can be done at the external level, so decl ma[3][3] = 1.5 is equivalent to decl ma = constant(1.5,3,3) (this is new in Ox 9). Just writing decl ma[3][3] creates a matrix of zeros, and is equivalent to decl ma = zeros(3,3).

Declaration statements do not have to occur at the start of a block. Consider for example:

test1(arg0) { decl k, a = arg0; decl [x1, x2] = {1, 2}; decl ident = <1, 0; 0, 1>; decl identsq = ident * ident; print("test\n"); decl i, j; for (i = 0; i < 10; i++) { test2(i); test3(j); // run-time error: j has no value }

Variables declared in an inner block hide variables in the outer block.

try-catch block and throw

A try-catch-block can be used to catch errors that are generated by the run-time system, or thrown elsewhere in the code. If an error occurs while executing the try block, program flow jumps to the catch block. The variable used as argument to catch is declared locally to the catch block, and contains an error value (often a string with the error message). The catch block can take corrective action, ignore the error, or throw an error again.

The throw statement can be used to throw an expression representing the error (as well as calling oxrunerror). The error expression can be of any type; run-time errors are always strings with the error message.

The following code

#include <oxstd.oxh> test(a) { if (a == 1) throw "throwing error"; if (a == 2) oxrunerror("run-time error"); unit(2) * unit(3); // this always fails } main() { try { test(1); println("no error"); } catch(e) { println("Caught error: ", e); } // println(e); // error: e only exists in the catch try { test(0); println("no error"); } catch(e) { println("Caught error: ", e); //throw e; // throw again: Ox aborts } println("normal continuation: ", meanc(rann(100,2))); }

prints:

Caught error: throwing error Caught error: Runtime error in test (10): 'matrix[2][2] * matrix[3][3]' bad operand normal continuation: -0.020860 -0.020586

Closed statement list

A statement list is closed if the only possible entry is at the top of the block, and the only exit at the bottom. So a closed block may not contain return or break to terminate a loop (thus leaving the block; but continue is allowed because it jumps to the next iteration, so stays within the loop). Neither may there be a jump statement into or out of the block.

Parallel programming

This section gives a summary of the use of parallel and serial. Examples are given in the Ox book.

Canonical for and foreach loops

A for loop is canonical if:

  1. the iterator is a local variable,
  2. the iterator is an integer,
  3. the iterator is not changed in the loop body,
  4. the iterator is incremented (or decremented) by an integer constant,
  5. the upperbound can be computed before the loop starts, In particular, it is either the value of a variable, or sizer, sizec, sizerc, sizeof, rows, columns of a variable.
  6. the upperbound is fixed while the loop executes,
  7. the loop body is a closed statement list.
Except for the last condition, all are automatically satisfied by a foreach loop. Ox can determine whether a for or foreach loop is canonical, and use compiled code for the iteration aspect, which is more efficient. If you use the -v command line switch, a message will indicate if a loop was optimized this way.

Parallel for and foreach loops

A loop statement can be prefixed with parallel to schedule it for parallel execution. However, only a canonical for or foreach loop can be run in parallel. Moreover, a programmatic requirement is that there is no dependency between iterations, i.e.~if the ordering of the iterations does not matter. This condition is not verified by Ox, but the responsibility of the programmer.

When Ox starts running code in parallel, n threads are created. Each thread gets its own space for local variables. Initially these are the same as the main thread (integers and doubles are copied, the remainder are references to the value in the main thread). As the threads proceed in parallel, the local variables may be different in each thread. When the parallel section is finished, only the local variables in the main thread survive, the others are removed. This is useful because it separates local variables, but is a problem for reduction operations such as accumulating a sum.

There is just one version of global variables. These are safe for reading, but writing (or writing and reading) in parallel is unsafe, resulting in a race condition. Or even a crash when memory allocation and deallocation overlaps.

Ox variables can be declared as serial, in which case only one thread at a time is able to modify the variable through the following compound assignment operations: *= /= += -= ~= |= .*= ./= ++ --. Note that simple assignment (=) is unaffected by the serial declaration.

decl i, j, crep = 10; decl sum1 = 0; parallel for (i = 0; i < crep; ++i) { sum1 += 1; } println("sum1=", sum1); serial decl sum2 = 0; parallel for (i = 0; i < crep; ++i) { sum2 += 1; } println("sum2=", sum2); prints sum1=3 sum2=10 The precise value of sum1 depends on the number of threads, i.e.what part is executed in the main thread. However, it clearly has not the intended result.

The value of sum2 is correct though: only one thread at a time was allowed to update, so, while one was doing this, the others had to wait. the price we pay for this is slower code.

Note that updating matrix elements is safe, provided the matrix is pre-allocated, and each iteration updates a different element. Also note that functions written in Ox code cannot be labelled as serial, but calls to dynamic-link libraries can.

Sections of code may need to be executed together in serial fashion. This can be achieved by creating a serial block. For example, to keep the print statements together:

parallel for (i = 0; i < crep; ++i) { // lengthy computation running in parallel // .... serial { print("i="); println(i); } }

Parallel computations are not-nested: if a parallel loop contains another parallel loop, the latter is executed serially. If a parallel loop is nested inside a serial section, it will not be executed in parallel. Specifying the -rp1 Ox command line switch also forces the program to run serially.

It is possible to make parallel execution conditional in your code:

decl do_in_parallel = TRUE; parallel if(do_in_parallel) for (i = 0; i < crep; ++i) { }

Expressions

Operator tables:
Operator precedence
Result from dot-style binary operators
Result from relational operators
Result from operators involving an empty matrix as argument
Result from relational operators involving missing values

Table syn.2: Operator precedence

Category operators associativity primary () :: [] left to right postfix -> . () [] ++ -- ' left to right power ^ .^ left to right unary ++ -- + - ! & new delete right to left multiplicative ** * .* / ./ left to right additive + - left to right horizontal concat. ~ left to right vertical concat. | left to right relational < > <= >= < > <= >= left to right equality == != .== .!= left to right logical dot-and .&& left to right logical-and && left to right logical dot-or .|| left to right logical-or || left to right conditional ? : .? .: right to left assignment = *= /= += -= ~= |= .*= ./= right to left comma , left to right

Table syn.2 gives a summary if the operators available in Ox, together with their precedence (in order of decreasing precedence) and associativity. Operators on the same line have the same precedence, in which case the associativity gives the order of the operators. Parentheses can change the order of evaluation:

i = 1 + 2 * 3; // 7 i = (1 + 2) * 3; // 9

Subsections below give a more comprehensive discussion. Several operators require an lvalue, which is a region of memory to which an assignment can be made. Note that an object which was declared const is not an lvalue. Many operators require operands of arithmetic type, that is int, double or matrix.

The elemental operators are dot-operators (operating element-by-element) and relational operators (operating element by element, but returning a single boolean value). The resulting value is given Tables syn.3 and syn.4 respectively. In addition, there are the matrix operations, especially matrix multiplication and division; the result from these operators is explained below. Note that a scalar is defined as: int, double or $1 \times 1$ matrix.

Table syn.3: Result from dot-style binary operators, + - .* ./ .^ .<< .|| .< .> .<= .>= .== .!=

left a .op right b result computes int .op int int a op b int/double .op double double a op b double .op int/double double a op b scalar .op matrix m x n matrix m x n a op b_{ij} matrix m x n .op scalar matrix m x n a_{ij} op b matrix m x n .op matrix m x n matrix m x n a_{ij} op b_{ij} matrix m x n .op matrix m x 1 matrix m x n a_{ij} op b_{i0} matrix m x n .op matrix 1 x n matrix m x n a_{ij} op b_{0j} matrix m x 1 .op matrix m x n matrix m x n a_{i0} op b_{ij} matrix 1 x n .op matrix m x n matrix m x n a_{0j} op b_{ij} matrix m x 1 .op matrix 1 x n matrix m x n a_{i0} op b_{0j} matrix 1 x n .op matrix m x 1 matrix m x n a_{0j} op b_{i0} string n .op string n matrix 1 x n a_{j} op b_{j} string n .op int matrix 1 x n a_{j} op i int .op string n matrix 1 x n i op b_{j}

Table syn.4: Result from relational operators < > <=; >=; == !=

left a op right b result computes int op int int 0,1 a op b int/double op double int 0,1 a op b double op int/double int 0,1 a op b scalar op matrix m x n int 0,1 a op b_{ij} matrix m x n op scalar int 0,1 a_{ij} op b matrix m x n op matrix m x n int 0,1 a_{ij} op b_{ij} string m op string n int 0,1 a op b array m == array n int 0,1 1 if m == n && a_{i} == b_{i} array m != array n int 0,1 !(a == b) object == object int 0,1 1 if referring to same object object != object int 0,1 1 if not referring to same object

Table syn.5: Result from relational operators involving an empty matrix as argument

operator a op <> <> op b <> op <> == 0 0 1 != 1 1 0 >= 0 0 1 > 0 0 0 <= 0 0 1 < 0 0 0 other <> <> <>


Table syn.6: Result from relational operators involving missing values

operator either argument .NaN both arguments .NaN == 0 1 != 1 0 >= 0 1 > 0 0 <= 0 1 < 0 0


Primary expressions

An expression in parenthesis is a primary expression. Its main use is to change the order of evaluation, or clarify the expression.

An expression in curly braces creates an array of the comma-separated expressions. See indexing in general and indexing with a string.

All types of constants form a primary expression.

The operator :: followed by an identifier references a variable declared externally. Examples are given. A class name followed by :: and a function member of that class references a static function member, or any function member if preceded by an object reference.

The this reference is only available inside non-static class member functions, and points to the object for which the function was called.

Multiple assignment and multiple returns

A comma-separated list of lvalues in square brackets can be used for multiple assignments. When the right-hand side is an array, each array value in turn is assigned to the next value of the left-hand side. The return value of a multiple assignment expression is zero (the examples below illustrate). When there is one lvalue in the square brackets, the right-hand side need not be an array. Few array elements on the right than lvalues on the left leads to a runtime error. The converse is no problem. Elements in the comma-separated list can be omitted, in which case the corresponding element on the right is skipped.

The following examples illustrate multiple assignments:

decl as; as = {"a", <10,11>, "b"}; decl [x1, x2, x3] = as; println("x1=", x1, " x2=", x2, "x3=", x3); [x1] = 10; [x2, x3] = {11,12,13}; //[x2, x3, x4] = {11,12}; // error: index out of range println("x1=", x1, " x2=", x2, " x3=", x3); [x1,,x3] = {21,22,23}; println("x1=", x1, " x3=", x3); x3 = 10 + ([x1, x2] = as[<0,2>]); println("x1=", x1, " x2=", x2, " x3=", x3); x1=<1,2,3,4>; [x1[0], x1[3]] = {-1, -3}; println("x1=", x1);

Which prints:

x1=a x2= 10.000 11.000 x3=b x1=10 x2=11 x3=12 x1=21 x3=23 x1=a x2=b x3=10 x1= -1.0000 2.0000 3.0000 -3.0000

A multiple assignment expression can be used to implement multiple returns from a function:

func(const r, const c) { return {zeros(r,c), ones(r,c)}; // array with 2 elements } main() { decl [x1, x2] = func(2, 2);//get element [0] in x1 and [1] in x2 println("x1=", x1, "x2=", x2, "as array: ", func(1, 1), "spread: ", ...func(1, 1)); }

Which prints:

x1= 0.00000 0.00000 0.00000 0.00000 x2= 1.0000 1.0000 1.0000 1.0000 as array: [0] = 0.00000 [1] = 1.0000 spread: 0.00000 1.0000

Lamda functions

A lambda function can be useful to create a local function with a different signature, or to provide access to local variables when the signature is proscribed (as e.g. the function for maximization). (This can also be achieved through a class, because a function member can access the members of the object to which it belongs, even when passed as an argument.) A lambda function can have arguments and local variables. It is somewhat different from a normal function: it has no function name (it is also called an anonymous function) but can be stored in a variable. Moreover, it has read-only access to all the local variables that are in the scope of its definition:

decl a, b; decl fnlam = [=](arg) { println("a=", a, " arg=", arg); return b; }; There are some restrictions: A lambda function can be created in place, from samples/maximize/probit1a: ir = MaxBFGS( [=](const vP, const adFunc, const avScore, const amHessian) { return fProbit(vP, adFunc, mx, my); }, &vp, &dfunc, 0, TRUE); But as a variable may be easier to read, see samples/maximize/probit1b: decl fprobit_max = [=](const vP, const adFunc, const avScore, const amHessian) { return fProbit(vP, adFunc, mx, my); }; ir = MaxBFGS(fprobit_max, &vp, &dfunc, 0, TRUE, maxctrl);

Postfix expressions

Member reference

The -> operator selects a member from an object reference. The left-hand expression must evaluate to a reference to an object, the right-hand expression must result in a member of that object. See classes.

Function calls

A function call is a postfix expression consisting of the function name, followed in parenthesis by a possibly empty, comma-separated list of assignment expressions. These provide the arguments for the parameters of the function. All argument passing is by value: they cannot be changed by the function that is called. The exception is an object or reference to a variable is passed: then the contents may be changed by the function. All arguments are evaluated from left to right before the function is entered. Recursive function calls are allowed. A function must be declared before it can be called, and the number of arguments in the call must coincide with the number in the declaration, unless

  1. the declaration has ... as the last argument, see declaration;
  2. the spread operator is used, see spread operator;
  3. the function has default values for missing arguments, see default arguments.

Some examples:

func1(a0, a1, a2, a3) { print("func1(", a0, ",", a1, ",", a2, ",", a3, ")\n"); } func2() { return 0; } func3(a0) { a0[0] = 1; } test1() { decl a, b; a = 1; func1(a, b = 10, func2(), a != 0); // func1(1,10,0,1) a = func2(); // a = 0 func3(&a); // a = 1 func3(a); // error }

In the latter example a will have been changed by func3. Function arguments are passed by giving the name of the function:

func4(a0, a1) { a1(a0); // make function call } func5(a0) { print("func5(", a0, ")\n"); } test2() { decl a = func5; func4(1, func5); // prints "func5(1)" func4(1, a); // prints "func5(1)" func4(1, func5(a)); // error: requires function func4(1, func2); // error: func2 takes incorrect } // number of arguments

Note that the parentheses in func5() indicate that it is a function call, whereas lack of brackets just passes the function itself.

Spread operator

The spread operator ... used as a prefix to an argument in a function call spreads an array into its constituent elements or matrix into columns. It is ignored otherwise. Here are some examples:

func1(const a, const b, const c, const d) { println("func1 received:", "%v", {a} ~ b ~ c ~ d); } main() { decl a = {1} ~ "two" ~ <1,2,3> ~ 4; func1(a[0], a[1], a[2], a[3]); func1(...a); func1(...a[:2], 99); func1(...a[:1], ...a[:1]); }

This prints:

func1 received:{1,"two",<1,2,3 >,4} func1 received:{1,"two",<1,2,3 >,4} func1 received:{1,"two",<1,2,3 >,99} func1 received:{1,"two",1,"two"}

Explicit type conversion

Explicit type conversion has the same syntax as a function call, using types int, double, matrix and string:

int double matrix string array v=0; v=0.6; v=<0.6,1>; v="tinker"; v={0, 0.6,"tin"}; matrix(v) <0> <0.6> v <116> <0,0.6,.> double(v) 0.0 v 0.6 error int(v) v 0 0 116 error array(v) {0} {0.6} {<0.6,1> {"tinker"} v

A single character is an integer, so integer 116 corresponds to the character 't'; string converts this back to a string:

for (decl i = 0; i < 26; ++i) println('a' + i, " as string: ", string('a' + i)); /* prints: 97 as string: a 122 as string: z */

Calling string on a function returns the function name; on an object the class name. Use sprint to get the string representation of a value (sprint("%v", to get it as the Ox code representation), and sscan to read a value from a string.

A matrix can be converted to an array and vice versa. To convert an array, all elements must be castable to a double (note that strings are scanned, a string that cannot be read is set to .NaN). A two-dimensional array can be converted to a two-dimensional matrix, but the array must be rectangular:

println("%v", matrix({{1,2,3},{"4","5","6"}})); // <1,2,3;4,5,6> println("%v", array(...<1,2,3>)); // {1,2,3}

Indexing vector and array types

Vector types (that is, string or matrix) and array types are indexed by postfixing square brackets. A matrix can have one or two indexes, a string only one. For an array type it depends on the level of indirection. Note that indexing always starts at zero. So a 2 by 3 matrix has elements:

[0][0] [0][1] [0][2] [1][0] [1][1] [1][2]

Three ways of indexing are distinguished:

indexing type matrix, string array example scalar yes yes m[0][0] matrix yes yes m[0][<0,1,2>] range yes yes m[][1:]

Starting with some examples:

decl m = <0,1,2; 10,11,12; 20,21,22>; // 0 1 2 // 10 11 12 // 20 21 22 println(m[0][0]); // prints 0 println(m[0][]); // prints 0.00000 1.0000 2.0000 println(m[1][1:]); // prints 11.000 12.000 println(m[.last][.last]); // prints 22 println(m[sizer(m) - 1][sizec(m) - 1]); // also prints 22 println("%v", m[.last-1 :][: .last-1]); // <10,11;20,21> println(m[sizer(m)][sizec(m)]); // index out of range println(m[.last + 1][.last]); // index out of range

In the first indexing case (allowed for all non-scalar types), the expression inside square brackets must have scalar type, whereby double is converted to integer.

Vector types may also be indexed by a matrix or have a range expression inside the brackets. In a matrix index to a string the first column of the matrix specifies the selected elements of the string.

It is possible to use only one index to a matrix. If a matrix x is a column or row vector, x[i] it will pick the ith element from the vector. If x is a matrix, it will treat the matrix as a vector (row by row, which corresponds to the vecr).

If a matrix is used as an index to a matrix, then each element (row by row, i.e. the vecr of the argument) is used as an index. As a consequence, indexing by a column vector or its transpose (a row vector) has the same effect. A matrix in the first index selects rows, a matrix in the second index selects columns. The resulting matrix is the intersection of those rows and columns.

A range index has the form start-index : end-index. Either the start-index or the end-index may be missing, which results in the lower-bound or upper-bound being used respectively. An empty index selects all elements. The resulting type from a range or empty index is always a vector type.

The magic value .last access the last element (its actual value is -256), and .last-1 the element before that (this is new in Ox 9).

Indexing beyond the end will result in a fatal run-time error. An exception is indexing a string for reference: this can be done one position beyond the end, which returns 0. For example, i=s[sizeof(s)] sets i to 0.

Some more examples:

decl mat = < 0:3; 10:13 >, d, m; decl str = "tinkertailor", s; decl arr = { "tinker", "tailor", "soldier" }; // mat = <0,1,2,3; 10,11,12,13> d = mat[0][0]; // d = 0 d = mat[1][2]; // d = 12 m = mat[1][]; // m = <10,11,12,13> d = m[1]; // d = <11> d = m'[1]; // the same: d = <11> d = mat[5]; // d = <11> m = mat[][2]; // m = <2; 12> m = mat[][]; // same as: m = mat; m = mat[0][<1:3>]; // matrix indexes columns: m = <1,2,3> m = mat[<1,0,1>][<1,3>]; // m = < 11,13; 1,3; 11,13 > mat[0][1:3] = 9; // range indexes columns: // mat = <0,9,9,9; 10,11,12,13> s = str[6:11]; // s = "tailor" str[6:11] = 'a'; // str = "tinkeraaaaaa" s = arr[1]; // s = "tailor" arr[1][0] = 'a'; // arr[1] = "aailor"

String indexing of array types

If an array consists of alternating strings and values, it can be indexed by a string. Indexing searches for the string in an even location, returning the next element (or .Null if not found). There are situations where this leads to more readable code. This is new in Ox 9.

decl arr = {"one": 1, "three": 3, "two": 2}; // same as: decl arr = {"one", 1, "three", 3, "two", 2}; println(arr["two"]); // prints 2 println(arr["four"]); // prints .Null

Postfix incrementation

A postfix expression followed by ++ or -- leads to the value of the expression being evaluated and then incremented or decremented by 1. The operand must be an lvalue and must have arithmetic type. For a matrix the operator is applied to each element separately. The result of the expression is the value prior to the increment/decrement operation.

Transpose

The postfix operator ' takes the transpose of a matrix. It has no effect on other arithmetic types of operands. Note that the single quote is also used in a character constant; the context avoids any ambiguity:

mat = m' * a'; mat = m'a'; // interpreted as m' * a' mat = m''; // two '' cancel out mat = m + 'a'; // 'a' is a character constant

Power expressions

The operands of the power operator must have arithmetic type, and the result is given in the table. If the first operand is not a matrix .^ and ^ are the same.

left a operator right b result computes int ^ .^ int or double int a^b int/double ^ .^ double double a^b double ^ .^ scalar double a^b scalar ^ .^ matrix m x n matrix m x n a^{b_{ij}} matrix m x n .^ int/scalar matrix m x n a_{ij}^b matrix m x n .^ matrix m x n matrix m x n a_{ij}^{b_{ij}} matrix m x m ^ scalar matrix m x m a^int(b)

When a and b are integers, then a ^ b is an integer if b >= 0 and if the result can be represented as a 32 bit signed integer. If b < 0 and a != 0 or the integer result would lead to overflow, the return type is double, giving the outcome of the floating point power operation.

The first line in the example shows that power has higher precedence than unary minus:

i = - 2 ^ 2; // i = -4 decl r, m1 = <1,2; 2,1>, m2 = <2,3; 3,2>; r = m1 .^ 3; // <1,8; 8,1> r = m1 .^ 3.7; // <1,12.996; 12.996,1> r = 3 .^ m1; // <3,9; 9,3> r = 3 ^ m1; // <3,9; 9,3> r = m1 .^ m2; // <1,8; 8,1> r = m1 ^ 3; // <13,14; 14,13> r = m1 ^ 3.7; // <13,14; 14,13> r = m1 ^ -3; // equivalent to: r = (1 / m1) ^ 3; r = m1 ^ m2; // error

Unary expressions

Prefix incrementation

A prefix expression preceded by ++ or -- leads to the lvalue being incremented or decremented by 1. This new value is the result of the operation. The operand must be an lvalue and must have arithmetic type. For a matrix the operator is applied to each element separately.

Unary minus and plus

The operand of the unary minus operator must have arithmetic type, and the result is the negative of the operand. For a matrix each element is set to its negative. Unary plus is ignored.

Logical negation

The operand of the logical negation operator must have arithmetic type, and the result is 1 if the operand is equal to 0 and 0 otherwise. For a matrix, logical negation is applied to each element. Negating a missing value returns 0, and negating an empty matrix returns an empty matrix.

j = 0; k = 10; i = !j; // i = 1 i = !k; // i = 0

Address operator

The operand of the address operator & must be an lvalue. In addition, it must be an object: it is possible to take the address of a class object, a function, or an array element, but not of a matrix element. The result is an array of one element, pointing to the region of space occupied by the lvalue. Referencing works through arrays; unlike C and C++ (but like the Java programming language), Ox does not have pointers. Some examples were in the section on function calls.

New and delete

The new operator can be used to create an object of a class, or to create a matrix, string or array. The delete operator removes an object created by new. Note that matrices, strings and arrays are automatically deleted when they go out of scope; this is also the case for objects (from Ox 9 onwards there is reference counting of objects). A class object can be removed explicitly using the delete operator. It must be deleted explicitly when two objects contain references to each other. Only one or two array levels at a time can be created by new; however, delete removes all sublevels. A string created by new consists of null characters, a matrix will have all elements zero, array elements will be .Null. Matrix, string and array objects with dimension zero are allowed (this can be useful to start concatenation in an iterative loop; remember that an empty matrix constant is <>, and an empty array {}). Matrices and arrays can be created with either one or two dimensions.

Examples involving objects of classes are given.

Multiplicative expressions

The operators **, *, .*, /, and ./ group left-to-right and require operands of arithmetic type. Strings are allowed in a limited way (new in Ox 9). These operators conform to Table syn.3, except for:

left a operator right b result computes matrix m x n * matrix n x p matrix m x p a_{i.}b_{.k} matrix m x n ** matrix p x q matrix mp x nq a_{ij}b scalar * matrix n x p matrix n x p ab_{ij} matrix m x n * scalar matrix m x n a_{ij} matrix m x n / matrix p x n matrix p x m a_{i.}b_{.k}^{+} scalar / matrix m x n matrix n x m ab_{ij}^{+} matrix m x n / scalar matrix m x n a_{ij}/b scalar / ./ scalar double a/b string m * scalar string m x b a ~ a ... ~ a string m * array p array p a ~ b_j array m * scalar array m x b a_0,...,a_{m-1},a_0,...,a_{m-1},... scalar * array m array m x b a_0,...,a_{0},a_1,...,a_{1},... array m * array p array m x p a_0~b_0,...,a_0~b_{p-1},a_1~b_1,... array m .* array m array m a_0~b_0,...,a_{m-1}~b_{m-1}

This implies that * ** are the same as .* when one or both arguments are scalar, and similarly for / and ./ when the right-hand operand is not a matrix.

Kronecker product is denoted by **. If neither operand is a matrix, this is identical to normal multiplication.

The binary * operator denotes multiplication. If both operands are a matrix, this is matrix multiplication and the number of columns of the first operand has to be identical to the number of rows of the second operand.

The .* operator defines element by element multiplication. It is only different from * if both operands are a matrix (these must have identical dimensions, however, if one or both of the arguments is a 1 x 1 matrix, * is equal to .*).

The product of two integers remains an integer. This means that overflow could occur (when it would not occur in operations where one of the argument is a double). For example 5000 * 50000 fits in an integer and yields 250,000,000, but 50000 * 50000 overflows, yielding -1.794,967,296. When using double arithmetic: 50000.0 * 50000 = 2500,000,000.0.

The binary / operator denotes division. If the second operand is a matrix, this is identical to post-multiplication by the inverse (if the matrix is square the matrix is inverted using the invert() library function; if that fails, or the matrix is non-square, the generalized inverse is used). If the second operand is a scalar, each element of the first is divided by it. If the first operand is a scalar, it is multiplied by the inverse of the second argument.

The ./ operator defines element by element division. If either argument is not a matrix, this is identical to normal division. It is only different from / if both operands are a matrix (these must have identical dimensions).

Note that / does not support integer division (such as e.g. 3 / 2 resulting in 1). In Ox, the result of dividing two integers is a double (3 / 2 gives 1.5). Integer division can be performed using the idiv library function. The remainder operator (% in C and C++) is supported through the library function imod. Multiplication of two integers returns an integer.

Some examples of multiplication and division involving matrices:

decl m1 = <1,2; 2,1>, m2 = <2,3; 3,2>, r; r = m1 * 2.; // <2,4; 4,2> r = 2. * m2; // <4,6; 6,4> r = m1 * m2; // <8,7; 7,8> r = m1 .* m2; // <2,6; 6,2> r = m1 .* <2,3>; // <2,6; 4,3> r = m1 ** m2; // <2,3,4,6; 3,2,6,4; 4,6,2,3; 6,4,3,2> r = 2 / 3; // 0.666667 r = 2 / 3.; // 0.666667 r = m1 / 2.; // <0.5,1; 1,0.5> r = m1 ./ <2;3>; // <0.5,1; 0.66667,0.33333> r = 2./ m2; // <-0.8,1.2; 1.2,-0.8> r = 2 ./ m2; // <1,0.66667; 0.66667,1> r = m2 / m2; // <1,0; 0,1> r = 1/<1;2>; // <0.2,0.4> r = 1/<1,2>; // <0.2; 0.4> r = 1/<0,0;0,0>; // <0,0; 0,0>

Notice the difference between 2./ m2 and 2 ./ m2. In the first case, the dot is interpreted as part of the real number 2., whereas in the second case it is part of the ./ dot-division operator. The white space is used here to change the syntax (as in the example in transpose); it would be more clear to write the second case as 2.0 ./ m2. The same difference applies for dot-multiplication, but note that 2.0*m2 and 2.0.*m2 give the same result.

Additive expressions

The additive operators + and - are dot-operators, conforming to Table syn.3. The exception is that adding strings amounts to concatenation, and subtraction involving strings is not allowed. Both operators group left-to-right. They respectively return the sum and the difference of the operands, which must both have arithmetic type. Matrices must be conformant in both dimensions, and the operator is applied element by element. For example:

decl m1 = <1,2; 2,1>, m2 = <2,3; 3,2>; r = 2 - m2; // <0,-1; -1,0> r = m1 - m2; // <-1,-1; -1,-1>

Concatenation expressions

left operator right result int/double ~ int/double matrix 1 x 2 int/double ~ matrix m x n matrix m x (1+n) matrix m x n ~ int/double matrix m x (n+1) matrix m x n ~ matrix p x q matrix max(m,p) x (n+q) int/double | int/double matrix 2 x 1 int/double | matrix m x n matrix (1+m) x n matrix m x n | int/double matrix (m+1) x n matrix m x n | matrix p x q matrix (m+p) x max(n,q) int ~ | string string string ~ | int string string ~ | string string array ~ | array array array ~ | any basic type array

If both operands have arithmetic type, the concatenation operators are used to create a larger matrix out of the operands. If both operands are scalar the result is a row vector (for ~) or a column vector (for |). If one operand is scalar, and the other a matrix, an extra column (~) or row (|) is pre/appended. If both operands are a matrix, the matrices are joined. Note that the dimensions need not match: missing elements are set to zero (however, a warning is printed of non-matching matrices are concatenated). Horizontal concatenation has higher precedence than vertical concatenation.

Two strings or an integer and a string can be concatenated, resulting in a longer string. Both horizontal and vertical concatenation yield the same result.

The result is most easily demonstrated by examples:

print(1 ~ 2 ~ 3 | 4 ~ 5 ~ 6); // <1,2,3; 4,5,6> print("tinker" ~ '&' ~ "tailor" ); // "tinker&tailor" print(<1,0; 0,1> ~ 2); // <1,0,2; 0,1,2> print(2 | <1,0; 0,1>); // <2,2; 1,0; 0,1> print(<2> ~ <1,0; 0,1>); // <2,1,0; 0,0,1>

The first two lines could have been written as:

print(<1,2,3; 4,5,6>); print("tinker" "&" "tailor" );

In the latter case, the matrix and string are created at compile time, whereas in the former case this is done at run time. Clearly, the compile time evaluation is more efficient. However, only the concatenation expressions can involve non-constant variables:

decl i1 = 1, i2 = 2, s1 = "tinke"; print(i1 ~ i2); // <1,2> print(s1 ~ 'r'); // "tinker"

Array concatenation results in an array with combined size, with assignment of each member of both arrays to the new array.

decl i, a1 = {"tinker", "tailor"}, a2 = {"soldier"}; a1 ~= a2; print(a1);

This prints:

[0] = tinker [1] = tailor [2] = soldier

Often, concatenation is required in a loop. In that case, it is convenient to start from a matrix of dimension zero, for example:

decl m, i; for (i = 0, m = <>; i < 4; ++i) m ~= i; print(m); // m = <0, 1, 2, 3>

Relational expressions

The relational operators are <, <=, >, >=, standing for `less', `less or equal', `greater', `greater or equal'. They all yield 0 if the specified relation is false, and 1 if it is true. The type of the result is always an integer, see Table syn.4. If both operands are a matrix the return value is true if the relation holds for each element. If one of the operands is of scalar-type, and the other of matrix-type, each element in the matrix is compared to the scalar, and the result is true if each comparison is true. Two arrays are equal if all dimensions are identical and all the entries are equal.

The dot relational operators are .<, .<=, .>, .>=, standing for `dot less', `dot less or equal', `dot greater', `dot greater or equal'. They conform to Table syn.3.

If both arguments are scalar, the result type inherits the higher type, so 1 >= 1.5 yields a double with value 0.0. If both operands are a matrix the return value is a matrix with a 1 in each position where the relation is true and zero If one of the operands is of scalar-type, and the other of matrix-type, each element in the matrix is compared to the scalar returning a matrix with 1 at each position where the relation holds.

String-type operands can be compared in a similar way. If both operands are a string, the results is int with value 1 or 0, depending on the case sensitive string comparison. Examples are given in the next section.

Equality expressions

The == (is equal to), != (is not equal to), .== (is dot equal to) and .!= (is not dot equal to) are analogous to the relational operators, but have lower precedence. The non-dotted versions conform to Table syn.4.

The dotted versions conform to Table syn.3.

For example:

decl m1 = <1,2; 2,1>, m2 = <2,3; 3,2>, s1 = "tinke"; print(m1 == 1); // 0 print(m1 != 1); // 0 print(!(m1 == 1)); // 1 print(m1 > m2); // 0 print(m1 < m2); // 1 print(s1 <= "tinker"); // 1 print(s1 <= "tink" ); // 0 print(s1 == "tinker"); // 0 print(s1 >= "tinker"); // 0 print(s1 == "Tinke"); // 0 print(m1 .== 1); // <1,0; 0,1> print(m1 .!= 1); // <0,1; 1,0> print(m1 .> m2); // <0,0; 0,0> print(m1 .< m2); // <1,1; 1,1> print("AACGTGGC" .== "ACCTTGGC"); // <1,0,1,0,1,1,1,1> print("AACGTGGC" .== 'A'); // <1,1,0,0,0,0,0,0>

The non-dotted versions only return true if the relation holds for each element. In the first two examples neither m1 == 1 nor m1 != 1 is true for each element, hence the return value 0. The third example shows how to test if a matrix is not equal to a value. The parenthesis are necessary, because ! has higher precedence than ==, and !m1 == 1 results in <0,0; 0,0> == 1 which is false.

The last four examples use dot-relational expressions, resulting in a matrix of zeros and ones. In if statements, it is possible to use such matrices. Remember that a matrix is true if all elements are true (i.e. no element is zero).

The any library function evaluates to TRUE if any element is TRUE, e.g.

evaluates to leads to if (any(m1 .== 1)) if (any(<1,0;0,1>)) if part if (any(m1 .!= 1)) if (any(<0,1;1,0>)) if part if (m1 == 1) if (0) else part if (m1 != 1) if (0) else part

Logical dot-AND expressions

The .&& operator returns 1 if both of its operands compare unequal to 0, 0 otherwise. Both operands must have arithmetic type. Handling of matrix-type is as for dot-relational operators: if one or both operands is a matrix, the result is a matrix of zeros and ones. Unlike the non-dotted version, both operands will always be executed. For example, in the expression func1() .&& func2() the second function is called, regardless of the return value of func1().

Logical-AND expressions

The a && b expression returns a if a is false (0 or .NaN, for a matrix: at least one 0 or .NaN, see Selection); in that case b is not evaluated. If a is true (not false), the expression returns b. In the expression func1() && func2() the second function will not be called if the first function returns false.

Logical dot-OR expressions

The .|| operator returns 1 if either of its operands compares unequal to 0, 0 otherwise. Both operands must have arithmetic type. Handling of matrix-type is as for dot-relational operators: if one or both operands is a matrix, the result is a matrix of zeros and ones. Unlike the non-dotted version, both operands will always be executed. For example, in the expression func1() .|| func2() the second function is called, regardless of the return value of func1().

Logical-OR expressions

The a || b expression returns a if a is true (not 0 or .NaN, for a matrix: no element is 0 or .NaN, see Selection); in that case b is not evaluated. If a is false, the expression returns b. In the expression func1() || func2() the second function will not be called if the first function returns true.

For example:

decl x = .NaN; println("0 || 9= ", 0 || 9); // 0 || 9= 9 println("2 || 9= ", 2 || 9); // 2 || 9= 2 println("x || 9= ", x || 9); // x || 9= 9 println("x || 0= ", x || 0); // x || 0= 0 println("0 || x= ", 0 || x); // 0 || x= .NaN println("0 && 9= ", 0 && 9); // 0 && 9= 0 println("2 && 9= ", 2 && 9); // 2 && 9= 9 println("x && 9= ", x && 9); // x && 9= .NaN println("x && x= ", x && x); // x && x= .NaN println("0 && x= ", 0 && x); // 0 && x= 0

Conditional expression

Both the conditional and the dot-conditional expression are ternary expressions.

For the conditional expression, the first expression (before the ?) is evaluated. If it is unequal to 0, the result is the second expression, otherwise the third expression. If there is no expression between \texttt{?} and \texttt{:}, the value of the logical expression that is left on the stack is used instead.

The dot-conditional expression only differs from the conditional expression if the first expression evaluates to a matrix, here called the test matrix. In that case the result is a matrix of the same size as the test matrix, and the test matrix can be seen as a filter: non zero elements get a value corresponding to the second expression, zero elements corresponding to the third expression. If the second or third expression is scalar, each matrix element will get the appropriate scalar value. If it is a matrix, the corresponding matrix element will be used, unless the matrix is too small, in which case the value 0. will be used. Note that in the dot-conditional expression both parts are executed, whereas in the conditional expression only one of the two parts is executed.

decl r, m2; r = <1,0; 0,1> ? 4 : 5; // 5, matrix is true if no element is 0 r = <1,0; 0,1> .? 4 .: 5; // <4,5; 5,4> m2 = <1>; r = r .== 4 .? m2 .: 0; // <1,0; 0,0>

Assignment expressions

The assignment operators are the simple assignment = as well as the compound *= /= += -= ~= |= .*= ./= operators. An lvalue is required as the left operand. The type of an assignment is that of its right operand. The compoundd assignment l op= r is equivalent to l = l op (r).

If the left-hand side is a comma-separated list in square brackets, the statement is a Multiple assignment expression.

The following code:

decl i, k; for (i = 0, k = 1; i < 5; i += 2) k *= 2, print("i = ", i, " k = ", k, "\n");

writes:

i = 0 k = 2 i = 2 k = 4 i = 4 k = 8

Assigning an object to another variable only passes a reference: both will refer to the same object. The clone library function makes a copy which should be removed using delete.

Comma expression

A pair of expressions separated by a comma is evaluated left to right, and the value of the left expression is discarded. The result will have type and value corresponding to the right operand. The example in the previous section has two instances of the comma operator in the for loop: i = 0, k = 1.

Constant expressions

An expression that evaluates to a constant is required in initializers and certain preprocessor expressions. A constant expression can have the operators * / + -, but only if the operands have scalar type. Some examples were given in sections on enumerations and external declarations.

File inclusion and preprocessing

Preprocessor commands in Ox are used for inclusion of files and conditional compilation of code. We follow the C or C++ convention although Ox does not use a real preprocessor: instead the commands are handled at the compiler level. They affect the handling of source code, but do not result in executable code by themselves. Note that escape sequences in strings literals are interpreted when used in preprocessor statements.

Using folder names in Ox

Folder names may have forward or backslashes (even mixed), both are handled as path separators. Escape sequences are not interpreted in include and import string below, except that \\ is translated to \.

Search path in Ox

The default search path in Ox is given by:
   user/OxMetrics9/ox/include
   user/OxMetrics9/apps
   user/OxMetrics9/ox
   install/OxMetrics9/ox/include
   install/OxMetrics9/apps
   install/OxMetrics9/ox
Where install is the installation folder (e.g. C:/Program Files), and user is your user folder. The user folders are only included if user/OxMetrics9/ox exists. Note that the default search path has changed going from Ox 8 to Ox 9 with the addition of the apps folder and the user folders. The default search path can be changed as follows

File inclusion

A line of the form

#include "filename"

will insert the contents of the specified file at that position. The file is searched for as follows:

A line of the form

#include <filename>

will skip the first step, and search as follows:

The quoted form is primarily for inclusion of user created header or code files, whereas the second form will be mainly for header files that are an integral part of Ox. The default extension for Ox header files is .oxh. (Up to version 6 the .h extension was used. For compatibility with older code, when a .h is included, the search is first for the file with a .oxh extension, and, if that fails, for the .h file.)

Note that escape sequences are interpreted in the include string, but not in the version which uses <...> (so in #include "dir\nheader.h", the \n is replaced by a newline character). Both forward and backslashes are allowed (use #include "dir/nheader.h", to avoid the newline character).

Import of modules

The #import preprocessor statement makes it easier to import compiled code modules. The statement can only happen at the external level, and has the form:

#import <modulename>

For example

#import <pcnaive>

has the following effect:

#include <pcnaive.oxh>
The header file is inserted at that location.
link the pcnaive.oxo file when the program is run, or if this is not found:
compile and link the pcnaive.ox file when the program is run.

Similarly:

#import "pcnaive"

has the following effect:

#include "pcnaive.oxh"
The header file is inserted at that location.
link pcnaive.oxo (or pcnaive.ox if the .oxo file is not found) when the program is run.

The import statement marks the file for linking, but that linking only happens when the file is executed. Even when a module is imported multiple times, it will only be linked in once. Similarly, the header file will not be included more than once in the same source code file. If the import name ends in a backward/forward slash, no header file is included, but the path will be searched when trying to find a DLL or loading a data file into Ox.

Conditional compilation

The first step in conditional compilation is to define (or undefine) identifiers:

#define identifier
#undef identifier

Identifiers so defined only exist during the scanning process of the input file, and can subsequently be used by #ifdef and #ifndef preprocessor statements:

#ifdef identifier
#ifndef identifier
#else
#endif

As an example, consider the following header file:

#ifndef OXSTD_INCLUDED #define OXSTD_INCLUDED // header statements #endif

Now multiple inclusion of the header file into a source code file will only once include the actual header statements; on second inclusion, OXSTD_INCLUDED will be defined, and the code skipped.

Pragmas

Pragmas influence the parsing process of the Ox compiler. Pragmas may only occur at the level of external declarations. The only pragma currently in use is _ox_stdlib and for internal use only.

Some difference with C and C++

There are some differences between Ox and C/C++ which might cause confusion:


Ox version 9. © JA Doornik This file last changed .