Syntax
In haXe, all expressions have the same level. It means that you can nest them together recursively without any problem. For example : foo(if (x == 3) 5 else 8). As this example shows, it also means that every expression returns a value of a given type.
Constants
The following constant values can be used :
0; // Int -134; // Int 0xFF00; // Int 123.0; // Float .14179; // Float 13e50; // Float -1e-99; // Float "hello"; // String "hello \"world\" !"; // String 'hello "world" !'; // String true; // Bool false; // Bool null; // Unknown<0> ~/[a-z]+/i; // EReg : regular expression
You will notice that null has a special value that can be used for any type, and has different behavior than Dynamic. It will be explained in detail when introducing type inference.
Operations
The following operations can be used, in order of priority :
v = e: assign a value to an expression, returnse+= -= *= /= %= &= |= ^= %%<<%%= %%>>%%= %%>>>%%=: assign after performing the corresponding operatione1 || e2: Ife1istruethentrueelse evaluatee2. Bothe1ande2must beBool.e1 && e2: Ife1isfalsethenfalseelse evaluatee2. Bothe1ande2must beBool.e1...e2: Build an integer iterator (see later about Iterators).== != > < >= %%<=%% === !==: perform normal or physical comparisons between two expressions sharing a common type. ReturnsBool.| & ^: perform bitwise operations between twoIntexpressions. ReturnsInt.%%<<%% %%>>%% %%>>>%%: perform bitwise shifts between twoIntexpressions. ReturnsInt.e1 + e2: perform addition. If both expressions areIntthen returnIntelse if both expressions are eitherIntorFloatthen returnFloatelse returnString.e1 - e2: perform subtraction between twoIntorFloatexpressions. ReturnIntif both areIntand returnFloatif they are eitherFloatandInt.e1 * e2: multiply two numbers, same return type as subtract.e1 / e2: divide two numbers, returnFloat.e1 % e2: modulo of two numbers, same return type as subtract.
Unary operations
The following unary operations are available :
!: boolean not. Inverse the expressionBoolvalue.-: negative number, change the sign of theIntorFloatvalue.++and----can be used before or after an expression. When used before, they first increment the corresponding variable and then return the incremented value. When used after, they increment the variable but return the value it had before incrementation. Can only be used withIntorFloatvalues.~: ones-complement of anInt.
Note: ~ is usually used for 32-bit integers, so it will not provide expected
results with Neko 31-bits integers, that is why it does not work on Neko.
Parentheses
Expressions can be delimited with parentheses in order to give a specific priority when performing operations. The type of ( e ) is the same as e and they both evaluate to the same value.
Blocks
Blocks can execute several expressions. The syntax of a block is the following :
{ e1; e2; // ... eX; }
A block evaluates to the type and value of the last expression of the block. For example :
{ f(); x = 124; true; }
This block is of type Bool and will evaluate to true.
As an exception, the empty block { } evaluates to Void.
Local Variables
Local variables can be declared in blocks using var, as the following samples show:
{ var x; var y = 3; var z : String; var w : String = ""; var a, b : Bool, c : Int = 0; }
A variable can be declared with an optional type and an optional initial value. If no value is given then the variable is null by default. If no type is given, then the variable type is Unknown but will still be strictly typed. This will be explained in details when introducing type inference.
Several local variables can be declared in the same var expression.
Local variables are only defined until the block they're declared in is closed. They can not be accessed outside the block in which they are declared.
Identifiers
When a variable identifier is found, it is resolved using the following order :
- local variables, last declared having priority
- class members (current class and inherited fields)
- current class static fields
- enum constructors that have been either declared in this file or imported
enum Axis { x; y; z; } class C { static var x : Int; var x : Int; function new() { { // x at this point means member variable this.x var x : String; // x at this point means the local variable } } function f(x : String) { // x at this point means the function parameter } static function f() { // x at this point means the class static variable } } class D { function new() { // x means the x Axis } }
Type identifiers are resolved according to the imported packages, as we will explain later.
Field access
Object access is done using the traditional dot-notation :
o.field
Calls
You can call functions using parentheses and commas in order to delimit arguments. You can call methods by using dot access on objects :
f(1,2,3); object.method(1,2,3);
New
The new keyword is used in expressions to create a class instance. It needs a class name and can take parameters :
a = new Array(); s = new String("hello");
Arrays
You can create arrays directly from a list of values by using the following syntax :
var a : Array<Int> = [1,2,3,4];
Please notice that the type Array takes one type parameter that is the type of items stored into the Array. This way all operations on arrays are safe. As a consequence, all items in a given Array must be of the same type.
You can read and write into an Array by using the following traditional bracket access :
first = a[0]; a[1] = value;
The array index must be of type Int.
If
Here are some examples of if expressions :
if (life == 0) destroy(); if (flag) 1 else 2;
Here's the generic syntax of if expressions :
if( expr-cond ) expr-1 [else expr-2]
First expr-cond is evaluated. It must be of type Bool. Then if true then expr-1 is evaluated, otherwise, if there is an expr-2 then it is evaluated instead.
If there is no else, and the if expression is false, then the entire expression has type Void. If there is an else, then expr-1 and expr-2 must be of the same type and this will be the type of the if expression :
var x : Void = if( flag ) destroy(); var y : Int = if( flag ) 1 else 2;
In haXe, if is similar to the ternary C a?b:c syntax.
As an exception, if an if block is not supposed to return any value (like in the middle of a Block) then both expr-1 and expr-2 can have different types and the if block type will be Void.
While
While are standard loops that use a precondition or postcondition :
while( expr-cond ) expr-loop; do expr-loop while( expr-cond );
For example :
var i = 0; while( i < 10 ) { // ... i++; }
Or using do...while :
var i = 0; do { // ... i++; } while( i < 10 );
Like with if, the expr-cond in a while-loop type must be of type Bool.
Another useful example will produce a loop to count from 10 to 1:
var i = 10; while( i > 0 ) { ....... i--; }
For
For loops are little different from traditional C for loops. They're actually used for iterators, which will be introduced later in this document. Here's an example of a for loop :
for( i in 0...a.length ) { foo(a[i]); }
Return
In order to exit from a function before the end or to return a value from a function, you can use the return expression :
function odd( x : Int ) : Bool { if( x % 2 != 0 ) return true; return false; }
The return expression can be used without argument if the function does not require a value to be returned :
function foo() : Void { // ... if( abort ) return; // .... }
Break and Continue
These two keywords are useful to exit early a for or while loop or to go to the next iteration of a loop :
var i = 0; while( i < 10 ) { if( i == 7 ) continue; // skip this iteration. // do not execute any more statements in this block, // BUT go back to evaluating the "while" condition. if( flag ) break; // stop early. // Jump out of the "while" loop, and continue // execution with the statement following the while loop. }
Exceptions
Exceptions are a way to do non-local jumps. You can throw an exception and catch it from any calling function on the stack :
function foo() { // ... throw new Error("invalid foo"); } // ... try { foo(); } catch( e : Error ) { // handle exception }
There can be several catch blocks after a try, in order to catch different types of exceptions. They're tested in the order they're declared. Catching Dynamic will catch all exceptions :
try { foo(); } catch( e : String ) { // handle this kind of error } catch( e : Error ) { // handle another kind of error } catch( e : Dynamic ) { // handle all other errors }
All the try and the catch expressions must have the same return type except when no value is needed (same as if).
Switch
Switches are a way to express multiple if...else if... else if test on the same value:
if( v == 0 ) e1 else if( v == foo(1) ) e2 else if( v == 65 ) e3 else e4;
Will translate to the following switch :
switch( v ) { case 0: e1; case foo(1): e2; case 65: e3; default: e4; }
Switches in haXe are different from traditional switches : all cases are separate expressions so after one case expression is executed the switch block is automatically exited. As a consequence, break can't be used in a switch and the position of the default case is not important.
On some platforms, switches on constant values (especially constant integers) might be optimized for better speed.
Switches can also be used on enums with a different semantic. It will be explained later in this document.
Local Functions
Local functions are declared using the function keyword but they can't have a name. They're values just like literal integers or strings :
var f = function() { /* ... */ }; f(); // call the function
Local functions can access their parameters, the current class statics and also the local variables that were declared before it :
var x = 10; var add = function(n) { x += n; }; add(2); add(3); // now x is 15
However, local functions declared in methods cannot access the this value. You then need to declare a local variable such as me :
class C { var x : Int; function f() { // WILL NOT COMPILE var add = function(n) { this.x += n }; } function f2() { // will compile var me = this; var add = function(n) { me.x += n }; } }
Anonymous Objects
Anonymous objects can be declared using the following syntax :
var o = { age : 26, name : "Tom" };
Please note that because of the type inference, anonymous objects are also strictly typed.
«« Basic Types - Type Inference »»