Sintaxis
En Haxe, todas las expresiones se encuentran al mismo nivel. Esto significa que puedes anidarlas recursivamente sin problemas. Por ejemplo : foo(if (x == 3) 5 else 8). De este ejemplo podemos extraer que cada expresión devuelve un valor de un tipo concreto.
Constantes
Se pueden utilizar los siguientes valores constantes :
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 : expresión regular
Observa que null tiene un valor especial que puede ser usado con cualquier tipo y su comportamiento es diferente al de Dynamic. Se explicará en detalle cuando se presente la inferencia de tipos.
Operaciones
Se pueden usar las siguientes operaciones, presentadas por orden de prioridad :
v = e: asignación de un valor a una expresión, devuelvee+= -= *= /= %= &= |= ^= <<= >>= >>>=: asignación tras realizar la operación correspondientee1 || e2: Sie1estrueentoncestruey si no, se evalúae2. Tantoe1comoe2deben serBool.e1 && e2: Sie1esfalseentoncesfalsey si no, se evalúae2. Tantoe1comoe2deben serBool.e1...e2: Construye un iterador de enteros (véanse los Iteradores más adelante).== != > < >= <=: realiza las comparaciones normales o físicas entre dos expresiones que comparten un tipo común. DevuelveBool.| & ^: realizan operaciones de bits entre dos expresionesInt. DevuelveInt.<< >> >>>: realiza desplazamientos de bits entre dos expresionesInt. DevuelveInt.e1 + e2: adición. Si ambas expresiones sonIntentonces devuelveInt; si una expresión esFloaty la otraIntoFloatentonces devuelveFloat; en cualquier otro caso devuelveString.e1 - e2: sustracción entre dos expresionesIntoFloat. DevuelveIntsi ambas sonInt; y devuelveFloaten cualquier otro caso.e1 * e2: multiplica dos números, devuelve el mismo tipo que la sustracció.e1 / e2: divide dos números, devuelveFloat.e1 % e2: módulo (resto) de dos números, devuelve el mismo tipo que la sustracción.
Opeaciones unarias
Se dispone de las siguientes operaciones unarias :
!: negación booleana. Invierte el valor de una expresiónBool.-: número negativo, cambia el signo de un valorIntoFloat.++y--se pueden usar antes o después de una expresión. Cuando se usan antes, primero incrementan la variable correspondiente y después devuelven el valor incrementado. Si se usan después, se incrementa la variable pero el valor devuelto es el que tenía antes del incremento. Sólo se puede usar con valoresIntoFloat.~: Complemento a uno de unInt.
Nota: ~ se usa normalmente con enteros de 32 bits, así que no proporcionará el resultado esperado con los enteros de 31 bits de Neko, razón por la cual no funciona en Neko.
Paréntesis
Las expresiones pueden ir delimitadas con paréntesis para especificar una cierta prioridad al realizar las operaciones. El tipo de ( e ) es el mismo de e y ambos se evalúan al mismo valor.
Bloques
Los bloques pueden ejecutar varias expresiones. La sintaxis de un bloque es la siguiente :
{ e1; e2; // ... eX; }
Un bloque se evalúa al tipo y valor de la última expresión del bloque. Por ejemplo :
{ f(); x = 124; true; }
Este bloque es de tipo Bool y se evaluará como true.
Como excepción, el bloque vacío { } se evalúa a Void.
Variables locales
Se pueden declarar variables locales dentro de los bloques usando var, de la manera mostrada en los siguientes ejemplos :
{ var x; var y = 3; var z : String; var w : String = ""; var a, b : Bool, c : Int = 0; }
Opcionalmente se puede declarar una variable indicando su tipo y su valor inicial. Si no se le da un valor, por omisión, la variable es null. Si no se indica un tipo, entonces la variable es de tipo Unknown pero aún así tiene un tipo estricto. Esto se explica en detalle al presentar la inferencia de tipos.
Se pueden declarar varias variables locales en la misma expresión var.
Las variables locales quedan definidas sólo hasta que se cierra el bloque en el que se declaran. No se puede acceder a ellas desde fuera del bloque en el que se declaran.
Identificadores
Cuando se encuentra el identificador de una variable, se resuelve siguiendo este orden :
- variables locales, teniendo más prioridad las últimas en haber sido declaradas
- miembros de clases (la clase actual y campos heredados)
- campos estáticos de la clase actual
- constructores de enumeraciones que tienen que haber sido importados o declarados en este fichero
enum Axis { x; y; z; } class C { static var x : Int; var x : Int; function new() { { // en este punto, x significa la variable miembro this.x var x : String; // en este punto, x significa la variable local } } function f(x : String) { // en este punto, x significa el parámetro de la función } static function f() { // x at this point means the class static variable } } class D { function new() { // x significa el Axis x } }
Los identificadores de tipo se resuelven de acuerdo con los paquetes importados, como se explicará más adelante.
Acceso a campos
El acceso a objetos se realiza mediante la tradicional notación de puntos :
o.campo
Invocación
Se puede invocar funciones usando paréntesis y comas para delimitar sus argumentos. Se puede invocar métodos usando el punto de acceso sobre los objetos :
f(1,2,3); objeto.método(1,2,3);
New
La palabra clave new se usa en expresiones para crear una instancia de una clase. Necesita un nombre de clase y puede tomar parámetros :
a = new Array(); s = new String("hola");
Arrays
Se pueden crear arreglos tomando directamente una lista de valores usando la siguiente sintaxis :
var a : Array<Int> = [1,2,3,4];
Observa que el tipo Array toma un parámetro de tipo que es el de los elementos almacenados dentro del Array. De esta manera todas las operaciones sobre arreglos son seguras. Como consecuencia, todos los elementos de un Array dado deben ser del mismo tipo.
Puedes leer y modificar un Array usando la siguiente notación tradicional de corchetes :
primero = a[0]; a[1] = valor;
Los índices de los arreglos deben ser de tipo Int.
If
Aquí tienes algunos ejemplos de expresiones if :
if (vida == 0) destruir(); if (flag) 1 else 2;
Ésta es la sintaxis genérica de las expresiones if :
if( expr-cond ) expr-1 [else expr-2]
Primero se evalúa expr-cond. Debe ser de tipo Bool. Entonces, si es true se evalúa expr-1, en caso contrario, si existe una expr-2 se evalúa ésta en su lugar.
Si no existe un else, y la expresión if es falsa, entonces la expresión en su conjunto tiene tipo Void. Si hay un else, entonces expr-1 y expr-2 deben ser del mismo tipo y éste será el tipo de la expresión if :
var x : Void = if( flag ) destruir(); var y : Int = if( flag ) 1 else 2;
En Haxe, if es similar a la sintaxis ternaria a?b:c de C.
Como excepción, si un bloque if no debe devolver ningún valor (por ejemplo, en mitad de un Bloque), entonces expr-1 y expr-2 pueden tener tipos diferentes y el bloque if será de tipo Void.
While
Los "mientras" son bucles estándar que usan una precondición o una poscondición :
while( expr-cond ) expr-bucle; do expr-bucle while( expr-cond );
Por ejemplo :
var i = 0; while( i < 10 ) { // ... i++; }
O, usando do...while :
var i = 0; do { // ... i++; } while( i < 10 );
Al igual que con if, la expr-cond de un bucle-mientras debe ser de tipo Bool.
Otro ejemplo útil producirá un bucle para contar de 10 a 1:
var i = 10; while( i > 0 ) { ....... i--; }
For
Los bucles "para" son ligeramente diferentes de los bucles for tradicionales de C. En realidad se usan con iteradores, que se presentarán más adelante. Éste es un ejemplo de bucle-para :
for( i in 0...a.length ) { foo(a[i]); }
Return
Para salir de una función antes de su final o para devolver un valor, se puede usar la expresión return :
function impar( x : Int ) : Bool { if( x % 2 != 0 ) return true; return false; }
La expresión return puede usarse sin argumentos si la función no precisa devolver un valor :
function foo() : Void { // ... if( abortar ) return; // .... }
Break y Continue
Estas dos palabras clave son maneras útiles de salir anticipadamente de un bucle for o while o de pasar a la siguiente iteración de un bucle :
var i = 0; while( i < 10 ) { if( i == 7 ) continue; // saltarse esta iteración. // no se ejecuta ninguna sentencia más en este bloque, // PERO se vuelve a evaluar la condición de ''while''. if( flag ) break; // parar anticipadamente. // Se sale del bucle ''while'' y se continúa // la ejecución en la sentencia que sigue al bucle. }
Exceptiones
Las excepciones son una manera de realizar saltos no locales. Puedes "lanzar" (throw) una excepción y "capturarla" (catch) en cualquier función de la pila :
function foo() { // ... throw new Error("foo no válido"); } // ... try { foo(); } catch( e : Error ) { // gestionar la excepción }
Puede haber varios bloques catch tras un try, para poder capturar diferentes tipos de excepciones. Se comprueban en el orden en que son declarados. Capturar Dynamic implica capturar todas las excepciones :
try { foo(); } catch( e : String ) { // gestiona este tipo de error } catch( e : Error ) { // gestiona otro tipo de error } catch( e : Dynamic ) { // gestiona todos los demás errores }
Todas las expresiones try y catch deben tener el mismo tipo de retorno excepto cuando no se necesita dicho valor (igual que con if).
Switch
Las expresiones switch son maneras de expresar comprobaciones if...else if... else if múltiples sobre un mismo valor :
if( v == 0 ) e1 else if( v == foo(1) ) e2 else if( v == 65 || v == 90 ) e3 else e4;
Se puede traducir al siguiente switch :
switch( v ) { case 0: e1; case foo(1): e2; case 65, 90: e3; default: e4; }
Nota: En el ejemplo anterior, una de las sentencias de caso indica "65, 90". Éste es un ejemplo en el que un caso espera coincidir con cualquiera de los dos (o más) valores, enumerados delimitándolos con comas.
Los switch de Haxe son diferentes a los tradicionales : todos los case son expresiones independientes de manera que tras ejecutarse una expresión, se sale automáticamente del bloque switch. Como consecuencia, no se puede usar break en un switch y el lugar en que se declare el caso default no es de importancia.
En algunas platformas, puede ser que se optimice la velocidad de los switches sobre valores constantes (especialmente sobre constantes enteras).
También se pueden usar switches con enum con una semántica diferente. Se explicará más adelante.
Funciones locales
Las funciones locales se declaran usando la palabra clave function pero no pueden tener un numbre. Son valores igual que un literal entero o una cadena :
var f = function() { /* ... */ }; f(); // invocamos la función
Las funciones locales pueden acceder a sus parámetros, a los miembros estáticos de la clase actual y a las variables locales que se declararon antes de ellas :
var x = 10; var add = function(n) { x += n; }; add(2); add(3); // ahora x es 15
Sin embargo, las funciones locales declaradas en métodos no pueden acceder al valor this. Para ello hace falta asignarlo a una variable local como yo :
class C { var x : Int; function f() { // NO COMPILAR? var add = function(n) { this.x += n; }; } function f2() { // compilará var yo = this; var add = function(n) { yo.x += n; }; } }
Objetos anónimos
Se pueden declarar objetos anónimos usando la siguiente sintaxis :
var o = { edad : 26, nombre : "Tom" };
Obsérvese que debido a la inferencia de tipos, los objetos anónimos también son de tipo estricto.
«« Tipos Básicos - Inferencia de tipos »»