Ox Syntax Reference

Chapter contents:

Lexical conventions
Comment

Identifiers
Keywords
Constants
Integer constants
Character constants
Double constants
Matrix constants
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 arguments
Variable length argument 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
Closed block
Parallel programming
Canonical for and foreach loops
Parallel for and foreach loops

Expressions
Primary expressions
Multiple assignment
Lambda functions
Postfix expressions
Member reference
Function calls
Explicit type conversion
Indexing vector and 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

Preprocessing
File inclusion
Import of modules
Conditional compilation
Pragmas

Difference with ANSI C and C++

Tables:

Escape sequences
Operator precedence
Result from dot 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 */

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 foreach operator static break delete goto parallel string case do if private struct char double inline protected switch class else int public switch_single const enum matrix return this continue extern namespace serial virtual decl for new short while

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). 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 (Not a Number).

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 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. An empty matrix can be writen as:

< >

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.

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 four derived types.

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 nearest representation will be used. 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.

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.

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.

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.

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

Functions

Function declarations

A function declaration communicates the number of arguments and their types to a file, 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 arguments must always be identical:

test0(); // function takes no arguments test1(const a1); // one const argument test2(const a2, a3); // two arguments, first is const static test3(a1); // cannot be used outside this file extern test4(a1); // function defined outside this file print(a1, ...); // variable number of arguments test1(a1); // error: previous declaration was different

A second form, which uses extern string-constant, provides dynamic linking of extension functions (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 64-bit version will try to load mydll_64 first, then try mydll; 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 32-bit mydll_64.dll Windows 64-bit mydll.so Linux 32-bit mydll_64.so Linux 64-bit mydll_osx.so OS X 32-bit mydll_sparc.so Solaris on Sparc, 32-bit mydll_sunx86.so Solaris on x86, 32-bit mydll_sparc_64.so Solaris on Sparc, 64-bit mydll_sunx86_64.so Solaris on x86, 64-bit

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: arguments declared const can be referenced, but cannot be changed inside the function. If the argument is a const reference, the reference cannot be changed, but what it references can. The decl keyword is optional in front of an argument. An empty argument list indicates that the function takes no arguments at all. The ... indicates a variable number of arguments; 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 arguments 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 the compiler will avoid this internally if the argument is not assigned to). For int, double, matrix and string types the whole object is copied. Any changes to the copy are lost as soon as the function returns. Derived types are accessed through a reference, and that reference is passed by value. However, what is pointed to may be changed, and that change will remain in effect after function return. So passing references allows a function to make a permanent change to a variable, for examples see function calls. It is good practice to label an argument const if a function doesn't change the variable.

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

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) // definition of threes { return {zeros(r,c), ones(r,c)}; // array with 2 elements } otherfunc() { decl x1, x2; [x1, x2] = func(3, 3);//get 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 an argument, all subsequent arguments 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 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 argument list

A special library function va_arglist() is used to access arguments in the variable argument list. It returns the arguments supplied for the ellipse as an array. An example illustrates:

test(const a, ...) { decl i, args = va_arglist(); for (i = 0; i < sizeof(args); i++) print (" vararg ", i, ": ", args[i]); } main() { test("tinker", "tailor", "soldier"); }

which prints vararg 0: tailor vararg 1: soldier.

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, m_y; // two data members const decl m_origin; // const data member static decl sm_cLines; // static data member Line(const orig); // 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 arguments, subject to the restriction that, when a default value is supplied for an argument, all subsequent arguments must have a default value. Default arguments are added to the call at run time.

Data members that are static const must be initialized in the class declaration. Data members that are not static but are const can only be initialized in the constructor function. Otherwise data members can be initialized in the constructor function, or anywhere else they are accessible.

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.

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 arguments; they must be specified in the declarations 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 -> or 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 -> or .

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.

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. If the constructor function is absent, a default constructor function will be assumed which takes no arguments. A constructor 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).

A destructor 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 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(0); // 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(0);. 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++.

Selection statements

The conditional expression in an if statement is evaluated, and if it evaluates to true (for a matrix: no element evaluates to false), the statement is executed. Zero (0), the empty matrix (<>) and a missing value (.NaN) 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.

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"); default: println("default"); }

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 (x in m[i][j]) 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 can be intitialized in a declaration statement. Variables in Ox are implicitly typed, and their type can change during program execution. Non-externally declared variables must be initialized before they can be used in an expression. It is not possible to specify matrix dimension as can be done at the external level, so instead of decl ma[3][3] = 1.5 write decl ma = constant(1.5,3,3). Unlike C, declaration statements do not have to occur at the start of a block. Consider for example:

test1(arg0) { decl k, a = arg0; 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); // error: j has no value }

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

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). 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 canonical for or foreach loop can be run in parallel (Ox Professional only) if 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 user can label a loop as parallel.

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

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. 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. Relatedly, any parallel loops inside a serial section will not be executed in parallel. Specifying the -rp1 Ox command line switch also forces the program to run serially.

Expressions

Operator tables:
Operator precedence
Result from dot 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. The precedence is in decreasing order. Operators on the same line have the same precedence, in which case the associativity gives the order of the operators. Note that the order of evaluation of expressions is not fully specified. In:

i = a() + b();

it is unknown whether a or b is called first. 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 most common 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 special matrix operations, such as matrix multiplication and division; the result from these operators is explained below. A scalar consists of: int, double or 1 x 1 matrix.

Table syn.3: Result from dot 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 a op b int/double op double int a op b double op int/double int a op b scalar op matrix m x n int a op b_{ij} matrix m x n op scalar int a_{ij} op b matrix m x n op matrix m x n int a_{ij} op b_{ij} matrix m x n op matrix m x 1 int a_{ij} op b_{i0} matrix m x n op matrix 1 x n int a_{ij} op b_{0j} matrix m x 1 op matrix m x n int a_{i0} op b_{ij} matrix 1 x n op matrix m x n int a_{0j} op b_{ij} string op string int a op b

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

operator a op <> <> op b <> op <> == FALSE FALSE TRUE != TRUE TRUE FALSE >= FALSE FALSE TRUE > FALSE FALSE FALSE <= FALSE FALSE TRUE < FALSE FALSE FALSE other <> <> <>


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

operator either argument .NaN both arguments .NaN == 0 1 != 0 1 >= 0 1 > 0 0 <= 0 0 < 1 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.

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

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. A multiple assignment expression can be used to implement multiple returns from a function.

The following examples illustrate multiple assignments:

decl x1, x2, x3, x4, as; as = {"a", <10,11>, "b"}; [x1, x2, x3] = as; println("x1=", x1, " x2=", x2, "x3=", x3); [x1] = 10; [x2, x3] = {11,12,13}; //[x2, x3, x4] = {11,12}; // error println("x1=", x1, " x2=", x2, " 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=a x2=b x3=10 x1= -1.0000 2.0000 3.0000 -3.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 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 also 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. All argument passing is by value, but when an array is passed, its contents may be changed by the function (unless they are const). The order of evaluation of the arguments is unspecified; all arguments are evaluated 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 the declaration has ... as the last argument.

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.

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 v=0; v=0.6; v=<0.6,1>; v="tinker"; matrix(v) <0> <0.6> v <116> double(v) 0.0 v 0.6 see below int(v) v 0 0 116

The double to string conversion function and its reverse are for packing string values in a double and subsequently extracting it. This is usually better avoided, as it restricts the string length to eight characters (more flexibility is offered by using arrays of strings). Use the sprint library function to express double (or any other) value as a string. For example, double("tinker") packs the string in a double value. Since a double is 8 bytes, the string is truncated at 8 characters (or padded by null characters). Conversely, string(dbl) extracts the string from a double value, automatically appending a null character. Calling string on a function returns the function name; on an object the class name.

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:]

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.

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

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 removed when they go out of scope; this is not the case for objects. A class object, on the other hand, must be removed explicitly using the delete operator. If not, it will exist until the program terminates (which may be acceptable). 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. 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 not allowed. 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

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.

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 && operator returns the integer 1 if both of its operands compare unequal to 0, and the integer 0 otherwise. Both operands must have arithmetic type. First the left operand is evaluated, if it is false (for a matrix: there is at least one zero element), the result is false, and the right operand will not be evaluated. So in the expression func1() && func2() the second function will not be called if the first function returned 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 || operator returns the integer 1 if either of its operands compares unequal to 0, integer value 0 otherwise. Both operands must have arithmetic type. First the left operand is evaluated, it it is true (for a matrix: no element is zero), the result is true, and the right operand will not be evaluated. So in the expression func1() || func2() the second function will not be called if the first function returned true.

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.

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.

Preprocessing

Preprocessing in Ox is primarily used for inclusion of files and conditional compilation of code. As such it is more restricted than the options available in C or C++. Escape sequences in strings literals are interpreted when used in preprocessor statements.

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. Defined is:

#pragma array_base(integer)

As discussed at various points, indices in matrices, arrays and strings always start at 0. This is the C and C++ convention. Ox, however, allows circumventing this convention by using the array_base pragma. Library functions which return a set of indices, are aware of the array_base settings, and will return appropriate values. It is recommended to adopt the zero-based convention, and not use the array_base pragma.

Difference with ANSI C and C++

This section lists some of the differences between Ox and C/C++ which might cause confusion:


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