Syntax
In Haxe, all statements are expressions, which means they can be nested. For example, foo(if (x == 3) 5 else 8)
is valid Haxe. As this example shows, every expression returns a value of a given type.
Constants
Here are some examples of constants in Haxe:
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
Notice that null
has a special value, since any value can be null
. Notice also that it is not Dynamic
. This will be explained in detail when we introduce type inference.
Operations
The following operations can be used, in order of priority:
- Unary operations (see next section below).
e1 % e2
: calculates e1 modulo e2, ReturnInt
if they were bothInt
, otherwise returnFloat
.e1 / e2
: divide two numbers. Always returnFloat
.e1 * e2
: multiply two numbers. Same return type as modulo.e1 - e2
: SubtractInt
s orFloat
s. Same return type as modulo.e1 + e2
: perform addition. Same return type as modulo.<< >> >>>
: perform bitwise shifts between twoInt
expressions. ReturnsInt
.| & ^
: perform bitwise operations between twoInt
expressions. ReturnsInt
.== != > < >= <=
: perform normal or physical comparisons between two expressions sharing a common type. ReturnsBool
.e1...e2
: Build an IntIter frome1
toe2
- see Iterators for more information.e1 && e2
: Ife1
isfalse
thenfalse
else evaluatee2
. Bothe1
ande2
must beBool
.e1 || e2
: Ife1
istrue
thentrue
else evaluatee2
. Bothe1
ande2
must beBool
.- and
+= -= *= /= %= &= |= ^= <<= >>= >>>=
: assign after performing the corresponding operation v = e
: assign a value to an expression, returnse
Note: Associativity of operators can be found here here
Unary operations
The following unary operations are available :
!
: boolean not. Inverse the expressionBool
value.-
: negative number, change the sign of theInt
orFloat
value.++
and--
can be used before or after a variable. 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 withInt
orFloat
variables.~
: ones-complement of anInt
.
Note: ~
is usually used for 32-bit integers, so it is incompatible with Neko's 31-bits integers.
Parentheses
Surrounding an expression with parenthesis will give that expression higher priority when it is to be evaluated. The type of ( e )
is the same as e
, and they both evaluate to the same value.
Blocks
Blocks are a group of several expressions. The syntax of a block is:
{ 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
.
The empty block { }
evaluates to Void
.
Variable Naming
Variable names are case sensitive in Haxe. A valid variable name starts with a letter or underscore, followed by any number of letters, numbers, or underscores. As a regular expression, it would be expressed thus: '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'
Local Variables
Local variables can be declared in blocks using var
, as the following sample shows:
{ 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 (i.e. Haxe has lexical scoping).
Identifiers
When a variable identifier is resolved in the following order of precedence:
- 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
Member 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 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 from a list of values like this:
var a : Array<Int> = [1,2,3,4];
This is called an array literal.
Notice that Array takes one type parameter - the type of the items stored in the Array. All items in the array have to be of this type - this ensures type safety. (If you want a heterogeneous array, try Array<Dynamic>
— we'll learn about that in a bit.)
Multidimensional arrays can be created by nesting these type parameters in the declaration:
var a : Array<Array<Int>> = [ [1, 2, 3], [4, 5], [6, 7, 8, 9], // Terminal comma is permitted but ignored. ];
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
.
Also see Array Comprehension for more advanced array features.
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
. If it evaluates to 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. You can use ternary syntax if you want.
var x = ( x < upperBound )? x: upperBound;
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 different from traditional C for
loops, in that they can only be used with Iterators.
Here's an example of a for loop:
for( i in 0...a.length ) { foo(a[i]); }
If the iterator was created with ...
operator, with its second term smaller than or equal to the first, then the loop will not execute.
If the ...
operator is used as above (inline, inside the for), then a smaller second term will give a compiler error, explaining the problem.
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 an 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 "invalid foo"; } // ... try { foo(); } catch( e : String ) { // 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
).
See Handling Exceptions for more info.
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 || v == 90 ) e3 else e4;
Will translate to the following switch
:
switch( v ) { case 0: e1; case foo(1): e2; case 65, 90: e3; default: e4; }
Note: In the example above, a case statement reads "65, 90". This is an example where a case expects to match either of the two (or several) values, listed as delimited by comma(s).
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 different semantics. 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. To access "this", you 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 type inference, anonymous objects are also strictly typed.
«« Basic Types - Type Inference »»