Contents Index Summary Citation Doornik

Ox Syntax Reference

Contents:
Lexical conventions
Tokens
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
Specifiers
External variable declarations
Function declarations
Function definitions
Variable length argument list
Classes
Member function definitions
Constructor and destructor functions
The this pointer and member scope
Static members
Derived classes
Virtual functions
Statements
Selection statements
Iteration statements
Jump statements
Declaration statements
Expressions
Primary expressions
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
Conditional compilation
Pragmas
Difference with ANSI C and C++
Tables:
Escape sequences
Operator precedence
Result from dot operators
Result from relational operators


Lexical conventions

Tokens

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 decl extern new this break default for operator virtual case delete goto return while char do if short class double inline static const else int string continue enum matrix switch

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 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, which represents NaN (Not a Number), e.g.: < .,2,3; 4,.,6 > 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 pointers. 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.

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

Specifiers

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.

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

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, Pascal, etc.). This feature is not available on all platforms, and implementation is platform dependent. In the following example, test5 corresponds to the external function MyCFunc(), located in the dynamic library mydll. If this feature is supported, mydll will be automatically loaded, and the function imported.

extern "mydll,MyCFunc" test5(a1);

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. Arguments declared const cannot be changed inside the function. If the argument is a const pointer, the pointer cannot be changed, but what it points to can. 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. 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 pointer, and that pointer is passed by value. However, what is pointed to may be changed, and that change will remain in effect after function return. So passing pointers 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. This increases program clarity and enables the compiler to generate more efficient code.

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.

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. All data members are private, 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 x, y; // two data members const decl origin; // const data member static decl 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 }; // don't forget the ; 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 pointer to the object which is created using the new operator. Data members cannot be initialized in the class, but can in the constructor function.

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.

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

Line::Line(const orig) { x = y = orig; // set cursor at the origin origin = orig; // only allowed in constructor cLines++; // count number of Line objects } Line::moveto(const ax, const ay) { x = ax; y = ay; print("moved to ", ax, " ", ay, "\n"); return this; } Line::lineto(const ax, const ay) { // draw the line from (x,y) to (ax,ay) ... x = ax; y = ay; print("line to ", ax, " ", ay, "\n"); return this; } The new operator creates an object of the specified class, calls the constructor function, and returns a pointer to it. A member function is called through a member reference. 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 -> 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 pointer 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 pointer.

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 }

The this pointer 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 pointer to the newly created object. The assignment to x and 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->x = x; // assign arguments to data members this->y = 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.


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 is nonzero (for a matrix: no element is zero), the statement is executed. 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.

Iteration statements

The while statement excutes the substatement as long as the test expression is nonzero (for a matrix: at least one element is 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: at least one element is 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, unlike C++, the init_expr can not be preceded by a declaration.

Jump statements

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


Expressions


Table syn.2: Operator precedence

Category operators associativity primary () :: left to right postfix -> () [] ++ -- ' left to right unary ++ -- + - ! & new delete right to left power ^ .^ left to right 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.


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 int/double op matrix m x n matrix m x n a op b_{ij} matrix m x n op int/double 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} matrix m x n op matrix 1 x 1 matrix m x n a_{ij} op b_{00} matrix 1 x 1 op matrix m x n matrix m x n a_{00} op b_{ij}

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 int/double op matrix m x n int a op b_{ij} matrix m x n op int/double 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

Primary expressions

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

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

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

Postfix expressions

Member reference

The -> operator selects a member from an object pointer. The left-hand expression must evaluate to a pointer 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 <97> double(v) 0.0 v 0.6 see below int(v) v 0 0 97 Use double("tinker") to store 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 dbl (a null character is automatically appended). Storing strings in doubles or matrix elements is better avoided: more flexibility is offered by the string type.

Indexing vector and array types

Vector types (that is, string or matrix) and array types are indexed by postfixing square brackets. A matrix must always have 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 no m[0][<0,1,2>] range yes no 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.

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

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> 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. Some care is required when using variable names consisting of one character, in case the expression can be interpreted as a character constant: mat = m' * a'; mat = m' a'; // interpreted as m' * a' mat = m''; // two '' cancel out mat = m'a'; // error: 'a' is character constant, use m' a'

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. 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 or a function, but not of an array element, or matrix element. The result is an array of one element, pointing to the region of space occupied by the lvalue. Ox's limited pointer support works through arrays, the C indirection operator * is not supported. 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. Only one array level 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.

Examples involving objects of classes are given.

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 ^ .^ int/double double a^b int/double ^ .^ matrix m x n matrix m x n a^{b_{ij}} matrix m x n .^ int/double 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 ^ int/double matrix m x m a^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 second line in the example shows that unary minus has higher precedence than the power operator:

i = 1 - 2 ^ 3; // i = -7 i = 1 - - 2 ^ 3; // i = 9 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

Multiplicative expressions

The operators **, *, .*, /, and ./ group left-to-right and require operands of arithmetic type. 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 mn x nq a_{ij}b matrix m x n / matrix p x n matrix p x m a_{i.}b_{.k}^{+} int/double / matrix m x n matrix n x m ab_{ij}^{+} int/double / ./ int/double 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). 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).

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. 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 = 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;6,2> 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.666667,0.333333> r = 2./ m2; // <0.8,1.2; 1.2,-0.8> r = 2 ./ m2; // <1,0.666667; 0.666667,1> r = m2 / m2; // <1,-2.22045e-016; 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. 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 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. 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; for (i = 0; i < sizeof(a1); i++) print(a1[i]); prints tinkertailersoldier.

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. where it is false. 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, except when both arguments are a string, in which case the result is as for the non-dotted versions.

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 where it is false. 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, except when both arguments are a string, in which case the result is as for the non-dotted versions.

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> 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; // 4, <1,0; 0,1> has not all 0s 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 = *= /= += -= ~= |= symbols. An lvalue is required as the left operand. The type of an assignment is that of its left operand. The combined assignment l op= r is equivalent to l = l op (r).

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

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 system and library files. 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).

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 are:
#pragma array_base(integer)
#pragma link("filename")
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_basepragma. It is strongly recommended to adopt the zero-based convention, and not use the array_base pragma. More useful is the link pragma, which leads to inclusion of the named file (which should be compiled source code, with default extension .oxo) at the point of the pragma. This provides an alternative to specifying the link files on the command line. The search machanism is the same as for #include "filename". Link pragmas will normally occur in the same file as the main function; multiple linking of the same file will lead to errors: #include <oxstd.h> #pragma link("test.oxo") main() { // main code }

Difference with ANSI C and C++

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


Ox version 1.11. This file last changed 31-Jul-1996.