¿Qué es VR-SCRIPT?

VR-SCRIPT es el lenguaje de scripting nativo de los MSXVR y con el que se pueden desarrollar cualquier tipo de aplicaciones y juegos. Además de la versatilidad del script, permite una programación orientada a objetos entre otras particularidades especiales. Este lenguaje utiliza una sintaxis similar a la del C/C++. Dentro del MSXVR se lanzan programas que ejecutan estos scripts. Estos programas tienen acceso a un conjunto de funciones nativas para poder desarrollar todo tipo de tareas.

Palabras reservadas

_change, _call, _implements, _instanceof, _set, _unset, _get, _state, _sizeof, _eval, _run, break, case, class, constants, continue, default, defines, delete, do, else, exit, externals, for, function, false, float, globals, if, import, implements, int, in, null, new, namespace, native, properties, pointer, protected, private, public, partial, package, return, rep, state, switch, string, sizeof, select, true, this, typeof, until, virtual, while, when

Estructura del archivo

VR-SCRIPT opera con archivos individuales, donde cada archivo hace referencia a una clase (aunque puede incluir subclases). Por defecto el sistema no es sensible a mayúsculas/minúsculas (aunque se puede configurar para que sí lo sea) y permite el uso de directivas de preprocesamiento. La estructura básica de un archivo sería:

namespace IDENTIFIER
import [package] STRING

class IDENTIFIER[<INTERFACE>] [partial STRING [, STRING, ...]] [implements STRING [, STRING, …]]
{
   globals:
      IDENTIFIER [: TYPE] = VALUE;
   ...
   externals:
      IDENTIFIER;
   ...
   defines:
      IDENTIFIER [: TYPE] [= VALUE];
   ...
   [private | public | protected] constants:
      IDENTIFIER [: TYPE] [= VALUE];
   ...
   [private | public | protected] properties:
      IDENTIFIER [: TYPE] = VALUE;
   ...
   [private | public | protected] {function | virtual} NAME ([[&]ARG,[&]ARG,…]) [: TYPE] {}
   [private | public | protected] state [virtual] STRING[:] {}
}

Tipos

Existen diferentes tipos de datos en VR-SCRIPT. Son los siguientes:

NombreTYPETamaño (Bytes)Rango
boolTYPE_BOOL1True | False
byteTYPE_BYTE1-128 hasta 127
ubyteTYPE_UBYTE10 hasta 255
shortTYPE_SHORT2-32768 hasta 32767
ushortTYPE_USHORT20 hasta 65535
intTYPE_INT4-2147483648 hasta 2147483647
uintTYPE_UINT40 hasta 4294967295
longTYPE_LONG8-263 hasta 263-1
ulongTYPE_ULONG80 hasta 264
floatTYPE_FLOAT41.2E-38 hasta 3.4E+38 (6 dec)
doubleTYPE_DOUBLE82.3E-308 hasta 1.7E+308 (15 dec)
listTYPE_LIST
bufferTYPE_BUFFER
stringTYPE_STRING
pointerTYPE_POINTER
objectTYPE_OBJECT

Ejemplos de inicialización:

A = 5;
B = 6.5;
C = "Hola";
D = [A, B, "CD", [1, 2]];
E = null;
// Defino un Buffer de 32 bytes
F = {@32@}
// Defino un Buffer de 9 bytes con 9 valores
G = {5,6,4,3,4,5,6,3,2}
// Defino un Buffer de 128 bytes con 4 valores
H = {@128@10, 4, 5, 3}
// Inicializo la variable I como booleano
I = true;

sizeof / typeof

Con «sizeof» podemos saber el tamaño o cantidad de elementos y con «typeof» el tipo de la variable o constante.

Ejemplos:

b = sizeof(5);b = 4Tamaño del entero en bytes
c = sizeof(6.2);c = 4Tamaño del flotante en bytes
d = sizeof(«Hola»);d = 4Número de caracteres
e = sizeof(null);e = 0Valor de NULL
a = «Hola»;b = TYPE_STRINGTipo de la variable \
b = typeof(a);

Es posible programar forzando la especificación de tipo. Para ello usaremos la sintaxis:

#pragma typejuggling=off

Esto obligará a determinar el tipo en cada declaración de constantes, propiedades, parámetros de funciones y valores de retorno de función. También es posible definir variables con un tipo concreto usando:

properties:
    a = int;
    b = byte;
    c = bool;

Representación numérica

Veamos diferentes formas de expresar el mismo número:

InicializaciónTipoBytes Tamaño
a = 15;Entero (int)4
a = 15L;Entero (long)8
a = 15.0f;Flotante (float)4
a = 15.0;Flotante (float)4
a = 15.0L;Flotante doble (double)8
a = 0xF;Hexadecimal (int)4
a = 0b1111;Binario (int)4
a = 0o17;Octal (int)4
a = 0xFL;Hexadecimal (long)8
a = 0b1111L;Binario (long)8
a = 0o17L;Octal (long)8

Propiedades

Las propiedades son variables y constantes, que tienen un identificador (cadena de caracteres) y un ámbito de acceso, bien sea global (entre clases) o local a la clase.

Variables y constantes globales

Es posible definir variables globales accesibles desde cualquier instancia en ejecución. Las variables globales existen en el momento en que un script las crea y mientras la instancia de dicho script perdure activa (no se borre). Para crearlas es necesario definirlas en la sección GLOBALS de la clase.Del mismo modo, es posible crear etiquetas (o variables globales de solo lectura) accesibles desde cualquier script. Para ello usaremos la sección DEFINES dentro de la clase del script. Una vez creadas, al igual que las variables globales, son accesibles desde cualquier script en ejecución hasta que
el objeto creador sea eliminado del sistema.Las variables y constantes globales pueden tener el mismo nombre en diferentes namespaces (ámbitos) sin que haya colisión. Véase NameSpaces. Dentro del mismo ámbito podemos tener diferentes scripts que creen las mismas variables o constantes globales. Eso no es un error y la variable/constante
global no se creará dos o más veces, siempre se utilizará la misma.

Ejemplo:

namespace com.retromaking.swords;
class Slurp implements Enemy
{
   globals:
      DEFAULT_HIT = 3;
   defines:
      DEFAULT_LIFE = 100;
      DEFAULT_ITEM = "Random";
}

Properties y Constants

Las propiedades de clase solo son accesibles desde el script que las contiene. Aunque tengamos varias instancias de la misma clase funcionando a la vez, cada instancia tendrá sus propios valores.Estas propiedades pueden ser de lectura/escritura o constantes (solo lectura). En el caso de ser constantes se crearán bajo la sección CONSTANTS, y en caso de ser de lectura/escritura bajo la sección PROPERTIES. En ambos casos, podemos definir si su acceso es:

  • PRIVATE: Solo accesible desde la clase que las crea.
  • PROTECTED: Accesible por la clase que las crea e hijas.
  • PUBLIC: El caso por omisión, permite el acceso desde cualquier clase.

Para más información, véase Herencia de clases.

Locales

Son aquellas que se definen en funciones o estados y solo son accesibles desde esa función o estado.

class A
{
    properties:
          X = 5;

    function A()
    {
        _p = 6 + X;
    }
    function B()
    {
        Print(_p);
        _c = 8 + X;
    }
}

Vemos que la propiedad es accesible desde cualquier función. Sin embargo, en el caso de «_p», que se ha definido en la función A, obtendríamos un error de compilación ya que «_p» no existe en la función B.

Namespaces

La función del namespace es definir ámbitos de uso para las variables globales, etiquetas y clases. Si definimos una clase con un namespace, las variables globales y etiquetas creadas se asociarán bajo dicho namespace, por lo que no colisionarán con variables globales o etiquetas con el mismo nombre definidas en otros namespaces.

Acceso por nombre a propiedades

Mediante las sentencias _get y _set podemos leer o escribir valores en propiedades de instancia.

_get ("a");

En el caso de que «a» no exista como propiedad, se dará un aviso de propiedad no encontrada y la función _get devolverá el valor entero cero.

_set ("a", 5);

Si la propiedad ‘a’ no existe, se creará y la función devolverá el valor 2.Si la propiedad ‘a’ ya existe, se asignará el valor y la función devolverá el valor 1.Si la propiedad ‘a’ ya existe y es una constante, no se asignará el valor y la función devolverá el valor 0.Con la sentencia _unset podemos eliminar las propiedades creadas.

_unset ("a");

En caso de que la propiedad exista, devolverá verdadero y se eliminará. Si por el contrario, la propiedad no existe, devolverá falso.

Funciones

Mediante las funciones creamos el comportamiento de las clases. Es el mecanismo con el cual podemos escribir código dentro de las clases.

Polimorfismo

Una clase puede crear el número que quiera de funciones. Estas funciones se identifican con un nombre y solo es posible usar el mismo nombre en dos o más funciones cuando el número de parámetros usado en cada una de ellas es distinto. A este mecanismo se le denomina Polimorfismo.

Virtuales

En caso de tener herencia (véase Herencia de clases), es posible que queramos sobrescribir una función heredada. Esto podemos querer hacerlo o bien sobrescribiendo completamente el comportamiento heredado con uno nuevo, o bien
ampliando o modificando dicho comportamiento. Supongamos que tenemos este ejemplo:

class Enemy
{
    properties:
        damage = 1;

    function SetDamage(_damage)
    {
        damage = _damage;
    }
}
class Troll implements Enemy
{
    properties:
        myDamage = 0;

    function SetDamage(_damage)
    {
        myDamage = _damage;
    }
}

Ahora vamos a ver distintas formas de aplicar la herencia de una función en la clase hija Troll. Para ello vamos a ver diferentes formas de declarar la función SetDamage dentro de la clase:

Sobrescribiendo el comportamiento

function SetDamage(_damage)
{
    myDamage = _damage;
}

Un ejemplo:

_a = new Troll();
_a.SetDamage(5);

Lo que hará será invocar a la función existente en la clase Troll. Por tanto solo se ejecutará el código de esa clase.

Heredando el comportamiento

virtual SetDamage(_damage)
{
    myDamage += 2;
}

Con el mismo ejemplo de antes, lo que ocurrirá es que primero se invocará la función «SetDamage» de Enemy y posteriormente el «SetDamage» de Troll.

Modificando el comportamiento

function SetDamage(_damage)
{
    if (_damage > 10)
        _damage = 10;
    ::SetDamage(_damage);
}

Y, siguiendo con el mismo ejemplo que los anteriores, en este caso controlamos cuándo se invoca «SetDamage» del padre.

Llamada a funciones por nombre

Mediante la sentencia _call (nombre_de_funcion, [arg1, arg2, …]); podemos llamar a funciones de clase usando una cadena de caracteres con el nombre de la función. Por ejemplo:

function Inicializar (base, nombre){}

function Salir (){}
…
_call ('Inicializar', [5, 'Parámetro']);
_call ('Salir');

El primer parámetro de la llamada es obligatorio y recibe una cadena que determina el nombre de la función a llamar. El segundo parámetro es opcional y recibe una lista con los argumentos a pasar en la llamada. Si invocamos nombres
de función que no existen o que tienen número de parámetros distintos, se generará un aviso pero no se detendrá la ejecución.

Funciones ocultas

Es posible crear funciones ocultas, sin nombre, que solo sirven para dar funcionalidad a una llamada concreta en forma de argumento.

_a = SetOnClick(this, function () (_sender, _event, _params)
{
        Print("Click received!");
});

El preprocesador

El preprocesador se encarga de preparar el código fuente para su posterior compilación. Lo que hace es modificar el código fuente y crear uno temporal que será el que finalmente se compilará. El preprocesador es útil para poder
compilar diferentes zonas de código en función de valores de estado, añadir código, definir macros, etiquetas y eliminar comentarios o texto que solo es útil al programador. A continuación se muestra la sintaxis disponible:

// Comentario de línea  Comentarios de línea. Terminan una vez alcanzan un retorno de carro o fin de archivo.
/* Comentario de bloque */Comentarios de bloque. Aglutina un comentario en una o varias líneas.
#include «path»  Incluye un archivo. Llegada esta instrucción del preprocesador, se incluirá el archivo referenciado por «path», que a su vez se volverá a preprocesar y compilar desde ese punto.
#filetolist «path»Incluye el contenido de un archivo como una lista de bytes. Esto nos permite hacer cosas como:
_a = #filetolist «image.png»
#define LABEL [VALUE]  Define una constante global con nombre LABEL y un valor VALUE. Este valor podrá ser cualquiera de los soportados por el lenguaje. En caso de omitir el valor, se usará el entero 1. Si la etiqueta ya se definió previamente, se sobrescribirá el valor.
#undef LABEL  Elimina la referencia de constante global con nombre LABEL.
#macro NAME CODE #endmacro  Establece una macro con nombre NAME. Cualquier referencia a dicha MACRO en el código, o sea, siempre que se detecte un uso de NAME en el código, se sustituirá por el texto CODE.
#macro NAME(ARGS) CODE #endmacro  Establece una macro con nombre NAME y argumentos ARGS. Los argumentos son identificadores separados por comas y que luego podremos usar en el texto CODE. Por tanto, cualquier referencia a la macro usando argumentos, si el número de argumentos coincide con el de la macro, se sustituirá por CODE y tendrá en cuenta los argumentos para aplicar las sustituciones correspondientes. Cuando usamos # delante de un nombre de un argumento, se sustituye no por la expresión pasada a la macro, sino por el mismo nombre.
#macro myMacro(x,y) x##y #endmacro
Sustituye por las expresiones x e y concatenadas.
#macro myMacro(x) x=#x #endmacro #x lo sustituirá por la expresión x convertida en texto. Viendo el ejemplo anterior: myMacro(var); se sustituirá por: var = «var»;
#pragma TYPE  En función de TYPE, permite activar o desactivar diferentes características usadas en tiempo de compilación. Véase sección Pragmas.
#metadata TEXT  Asocia metadatos a una sección del código compilado.
#region #endregionEstablece una región de código. Sirve para ofrecer meta-información para el uso en aplicaciones.  
#ifdebug #ifndebug #if CONDITION #elif CONDITION #else #endif  Permite decidir si queremos compilar trozos de código en función de si estamos en DEBUG o no lo estamos, o también dependiendo del valor de ciertas etiquetas (defines). Las condiciones pueden ser:
LABEL CONDITION && CONDITION
CONDITION || CONDITION
LABEL == VALUE
LABEL >= VALUE
LABEL <= VALUE
LABEL > VALUE
LABEL < VALUE
__DEBUG__  Se sustituye por 1 o 0 en función de si estamos compilando en modo depuración o no.
__LINE__  Se sustituye por el número de línea del código que se está compilando.
__FILE__Se sustituye por la ubicación del archivo de código que se está compilando.

Pragmas

Utilizado por la sintaxis de preprocesador #pragma, permite establecer diferentes condiciones y funcionalidades del compilador.

typejugglingObliga o no a la especificación de tipos en todas las declaraciones. Por defecto está desactivado (especificación de tipos no obligatoria).
strcasecmpSe indica si se quiere obligar a que se tengan en cuenta mayúsculas y minúsculas a la hora de establecer nombres de propiedades, funciones, etc. Por defecto está desactivado.
errorifnoreturnEn caso de que una función tenga que devolver algún valor, el compilador dará un error si no se especifica una sentencia \. En el caso de que queramos evitar esto, lo activaremos. Por defecto está activado.
errorifnoreturntypeEn el caso de que tengamos que devolver algún tipo concreto en una función y no lo hagamos, mediante este pragma evitamos que el compilador provoque un error. Por defecto está activado.
overrridefunctionsPermite sobrescribir funciones que se hayan declarado más de una vez dentro del mismo módulo. Esto quiere decir que si declaramos una función y posteriormente volvemos a declarar la misma función, la primera se anulará y el compilador no dará ningún error. Por defecto está desactivado.
constantspreserveEn caso de usar constantes en expresiones, el compilador las resuelve por sus valores. Mediante este pragma podemos hacer que el compilador no haga esto y obligue a hacer uso de la propiedad constante referenciada. Por defecto está desactivado.

Operadores

OpEnteroFlotanteStringLista
+SumarSumarConcatenarConcatenar
+=SumarSumarConcatenarConcatenar
RestarRestarQuitarQuitar
-=RestarRestarQuitarQuitar
++Incrementar unoIncrementar unoAñadir espacio al finalAñadir cero al final
Decrementar unoDecrementar unoQuitar último elementoQuitar último elemento
*MultiplicarMultiplicar
*=MultiplicarMultiplicarInsertar espacio en índiceInsertar cero en índice
/DividirDividir
/=DividirDividirQuitar elemento en índiceQuitar elemento en índice
/\División EnteraDivisión Entera
**PotenciaPotencia
**=PotenciaPotencia
%MóduloMódulo
%=MóduloMódulo
>>Desplazar bits derecha
>>=Desplazar bits derechaAñade espacios por el principio y quita caracteres por el finalAñade ceros por el principio y quita elementos por el final
<<Desplazar bits izquierda
<<=Desplazar bits izquierdaAñade espacios por el final y quita caracteres por el principioAñade ceros por el final y quita elementos por el final
==¿Es igual?¿Es igual?Comparación no sensible a mayúsculas y minúsculasComparación no sensible a mayúsculas y minúsculas
===¿Es igual?¿Es igual?Comparación sensible a mayúsculas y minúsculasComparación sensible a mayúsculas y minúsculas
>=¿Es mayor que?¿Es mayor que?¿Es mayor que?¿Número de elementos mayor?
<=¿Es menor que?¿Es menor que?¿Es menor que?¿Número de elementos menor?
!=¿Es distinto que?¿Es distinto que?¿Es distinto que?¿Es distinto que?
!Negación
&&AND Inclusivo
||OR Exclusivo
|OR bits
&AND bitsFiltrarFiltrar
^XOR bits
in¿Valor existente?¿Valor existente?
()Prioridad operaciónPrioridad operaciónPrioridad operaciónPrioridad operación
~NOT bitsInvertir la cadenaInvertir la lista

NOTA: La comparación con STRINGS se realiza carácter a carácter. Mientras son iguales, la comparación se resuelve como idéntica; en el momento en que un carácter es distinto a otro, se miran sus códigos ASCII para resolver si es
mayor o menor.

Operadores con bits (Flags)

En este manual existen muchas referencias al término «flags». Este término requiere conocer el concepto de activación, desactivación y consulta de bits (banderas) mediante el uso de números enteros.Los flags son siempre enteros potencia de dos: 1, 2, 4, 8, 16, 32, etc., de manera que en cada bit de un entero podemos guardar información sobre si una bandera (flag) está activa o no (vale 1 o 0). Por tanto, podremos almacenar tantas banderas como bits representen el número entero que vayamos a usar.Los operadores para trabajar con flags son el OR, AND, XOR y NOT.

A continuación mostraremos una tabla con ejemplos de acciones básicas con flags:

_flags = 0;Inicializamos los flags.
Tendremos 32 flags disponibles, ya que se inicializa como entero de 32 bits.
_flags = 1;Activamos el flag 0.
_flags = 32;Activamos el flag 5.
_flags &= ~1;Desactivamos el flag 0.
_check = (_flags & 32);¿Está el flag 5 activo?
_flags |=4 | 128 | 1024Activamos a la vez los flags 2, 7 y 10.
_flags &= ~(128 | 4)Desactivamos los flags 2 y 7.
_check = (_flags & (128 | 512));¿Están los flags 7 y 9 activos?

Strings

Un String hace referencia a un tipo de objeto que conforma una cadena de caracteres de 8 o 16 bits, dependiendo de su codificación: las cadenas de 8 bits se gestionan como ASCII y las de 16 bits como UNICODE.

Ejemplos de operaciones con strings

OperaciónAcciónResultado
A = «123»Inicializamos con un string en formato ASCIIZA = «123»
A += «4»Agregamos la cadena «4»A = «1234»
A -= «23»Quitamos la cadena «23»A = «14»
«6» in ABusca la cadena «6» en AFALSE
«4» in ABusca la cadena «4» en ATRUE
«34» in ABusca la cadena «34» en ATRUE
A ++Agrega un espacio al finalA = «34 «
A —Quita el último carácterA = «34»
A += 5Añade 5 espacios al finalA = «34     «
A -= 5Quita 5 espacios al finalA = «34»
A *= 1Inserta un espacio en la posición 1A = «3 4»
A /= 1Quita un carácter en la posición 1A = «34»
A >>= 1Añade un espacio por la izquierda y elimina el carácter finalA = » 3″
A <<= 1Añade un espacio por la derecha y elimina el primer carácterA = «3 «
A += ascii(‘A’)Añade un carácter ASCII a la cadenaA = «3 A»
A = ~AInvierte la cadenaA = «A 3»
A = «123»
B = «153»
C = A & B
Aplica un filtro de coincidenciaC = «13»
C = A – «2»Elimina «2» de la cadena AC = «13»
C = A + «2»Añade «2» a la cadena AC = «132»
«cad» == «ena»Compara las cadenas «cad» y «ena»FALSE
«cad» == «cad»Compara las cadenas (no sensible a mayúsculas)TRUE
«cad» == «cAd»Compara las cadenas (no sensible a mayúsculas)TRUE
«cad» === «cAd»Compara las cadenas (sensible a mayúsculas)FALSE
A = «cad» A[1]Accede al carácter de una cadena por índice‘a’ (número con valor 97)
sizeof(«cad»)Tamaño de «cad»3
strlen(«cad»)Tamaño de «cad»3
bool(«»)Convierte una cadena a boolFALSE
bool(«cad»)Convierte una cadena a boolFALSE
bool(«true»)Convierte una cadena a boolTRUE
int(«5»)Convierte una cadena a entero5
int(«cad»)Convierte una cadena a entero0
string(5)Convierte un entero a cadena«5»
float(«5.56»)Convierte una cadena a float5.56
string(7.8)Convierte un float a cadena«7.8»

NOTA: Del mismo modo que para bool, int y float, todos los tipos numéricos son susceptibles de poder ser convertidos de cadena al tipo y viceversa.

Listas

Un objeto «lista» está formado por una secuencia ordenada de valores. Los valores pueden ser de cualquiera de los tipos soportados por el VR-SCRIPT, incluyendo las propias listas.

Ejemplos de operaciones con listas

OperaciónAcciónResultado
A = []InicializaciónA = []
A = [10, 8]InicializaciónA = [10, 8]
A += [5]Añadir elementoA = [10, 8, 5]
A -= [5]Quitar elementoA = [10, 8]
A += [«cad»]Añadir elementoA = [10, 8, «cad»]
A += [10, 12]Añadir elementosA = [10, 8, «cad», 10, 12]
A += [[4, 5]]Añadir elementoA = [10, 8, «cad», 10, 12, [4, 5]]
6 in A¿Hay un 6 en A?FALSE
[10, 12] in A¿Hay un [10, 12] en A?TRUE
5 in A¿Hay un 5 en A?FALSE
A = [5, 4, 3]InicializaciónA = [5, 4, 3]
A ++Añadir un ceroA = [5, 4, 3, 0]
A —Quitar último elementoA = [5, 4, 3]
A += 5Añadir 5 cerosA = [5, 4, 3, 0, 0, 0, 0, 0]
A -= 5Quitar 5 últimos elementosA = [5, 4, 3]
A *= 1Añadir un cero en la posición 1A = [5, 0, 4, 3]
A /= 2Quitar elemento en la posición 2A = [5, 0, 3]
A >>= 1Desplaza la lista añadiendo un cero por la izquierdaA = [0, 5, 0, 3]
A <<= 1Desplaza la lista añadiendo un cero por la derechaA = [5, 0, 3, 0]
A = ~AInvierte la listaA = [0, 3, 0, 5]
A = [3, 5, 6]
B = [2, 5, 2, 7]
C = A & B
Aplicar filtro de coincidenciaC = [5]
C = A – [5]Quitar elemento coincidenteC = [3, 6]
C = A + [2]Añade elemento a final de la listaC = [3, 6, 2]
sizeof(c)Tamaño de la lista3
indexof(c, 6)Índice del elemento en la lista1
C[1]Acceso a la lista por índice6

Diccionarios

Un Diccionario es un tipo de lista. En este caso, denominaremos Diccionario a la lista que tiene componentes con el binomio CLAVE/VALOR, donde CLAVE es una cadena de caracteres y VALOR puede ser cualquiera de los tipos soportados por VR-SCRIPT. Ejemplo de inicialización:

A = [["barcelona", 1], ["madrid", 2], ["paris", 12], ["luxemburgo", 123]];
OperaciónAcciónResultado
A[«madrid»]Acceso por nombre2
sizeof(A)Número de elementos4
A /= 1Borrar elemento 1A = [[«barcelona», 1],[«paris», 12], [«luxemburgo», 123]];

Las operaciones con listas también son posibles con diccionarios y siguen las mismas reglas.

Estructuras de control

Mediante las estructuras de control vamos a gestionar qué código ejecutar y el número de veces que lo hará. En adelante veremos cómo se componen sintácticamente estas estructuras. Conviene saber qué significan los siguientes términos:

<CONDITION>Se trata de una expresión que al evaluarse podrá devolver VERDADERO o FALSO, o sea, un tipo booleano.
<SENTENCE>Hace referencia exclusivamente a una sentencia.
<SENTENCES>Hace referencia a una o varias sentencias de código. Si se trata de varias sentencias, tendremos que ponerlas entre los símbolos { } y además, al final de cada sentencia, el separador punto y coma ‘;’

En las estructuras de control con repeticiones podemos acabar creando bucles infinitos o sin salida si en algún momento no hacemos que la condición nos permita abandonar la iteración.

IF – ELSE IF – ELSE

Este tipo de estructuras permite controlar el flujo de ejecución del código usando condicionales. De esta manera, creamos ramas donde ejecutamos código en función de si ciertas condiciones dan verdadero o falso.

Formato:

if (<CONDITION>) <SENTENCES> [else if (<CONDITION>) <SENTENCES>] [else if (<CONDITION>) <SENTENCES>] … [else <SENTENCES>]

Ejemplo:

if (a == 5)
{
    print ("a es igual a 5");
}

WHILE

Nos permite repetir la ejecución de código mientras se cumpla cierta condición. Es posible que el código nunca llegue a ejecutarse si inicialmente la condición no se da.

Formato:

while (<CONDITION>) <SENTENCES>

Ejemplo:

a = 1;
while (a < 5)
{
    print ("dentro del bucle");
    a = a + 1
}

DO-WHILE

Esta versión del WHILE nos permite ejecutar el código al menos una vez, y continuar con la ejecución mientras se siga dando la condición.

Formato:

do <SENTENCES> while (<CONDITION>);

Ejemplo:

a = 5;
do
{
    a = a + 1;
    print ("dentro del bucle");
}
while (a < 5);

FOR

Este tipo de estructuras está pensado para iterar entre diferentes valores o repetir un código un número determinado de veces. Por un lado, la primera sentencia suele usarse para inicializar una variable índice. La condición sirve
para decidir la duración del bucle y la última sentencia define cómo varía el valor del índice. En cualquier caso, aunque suela ser el uso común del FOR, no significa que no se le puedan dar otros usos.

Formato:

for (<SENTENCE_S>;<CONDITION>;<SENTENCE_E>) <SENTENCES>

Ejemplo:

for (i=0; i<10; i++)
{
    print (tabla[i]);
}

Podríamos convertir un FOR en un WHILE del siguiente modo:

<SENTENCE_S>;
while (<CONDITION>)
{
    <SENTENCES>
    <SENTENCE_E>;
}

Se puede interrumpir la ejecución de estos bucles o ignorar una iteración usando las instrucciones BREAK y CONTINUE.

SWITCH

Este tipo de estructura está pensado para ejecutar diferentes partes de código en función de valores concretos o segmentos de valores. En la sentencia SWITCH se comprueban las condiciones existentes (CASE) una a una y se ejecuta el código de todas ellas a menos que usemos la sentencia BREAK, que cancelaría esta secuencia saliendo del ámbito de la sentencia SWITCH.

Formato:

switch (<EXPRESSION>) { case [==,<,<=,>,>=,in] <EXPRESSION>: <SENTENCES> [break;] case [==,<,<=,>,>=,in] <EXPRESSION>: <SENTENCES> [break;] … [default: <SENTENCES> [break;]] }

Ejemplo:

switch (a)
{
    case 5: print ("se trata de un error"); break;
    case < 12: 
    {
        print ("es un animal");
        v = lista_animales[a];
    }
    break;
}

REPEAT

Es una variante del WHILE y del FOR. Nos permite repetir código en base a una condición, o bien ejecutar código un número concreto de veces.

Formato:

repeat [while <CONDITION>] <SENTENCES>
repeat (<EXPRESSION>) <SENTENCES>

Ejemplo:

repeat (5) { print ("esto se ejecutará 5 veces"); }
i = 0;
repeat (i<5) { print ("esto también"); i++; }

BREAK, CONTINUE

Para el break:

DO/WHILECancela la ejecución del bucle y sale del mismo. Iría a la siguiente instrucción después del WHILE.
WHILECancela la ejecución del bucle y sale del mismo. Iría a la siguiente instrucción después del WHILE.
FORCancela la ejecución del bucle y sale del mismo. Iría a la siguiente instrucción fuera del ámbito del FOR.
SWITCHCancelar la ejecución de más código y sale del ámbito del SWITCH.

Para el continue:

DO/WHILEPermite cancelar la iteración volviendo a la primera línea de código tras el DO.
WHILEPermite cancelar la iteración volviendo a la primera línea de código tras el WHILE.
FOREjecuta el código que define la siguiente iteración y vuelve a la primera línea de código tras el FOR.

Tanto BREAK como CONTINUE solo tienen uso en las sentencias que se indican en las tablas anteriores. Usar estas instrucciones en otras sentencias dará un error de compilación.

Creación y borrado de instancias

Para crear instancias de objetos con VR-SCRIPT se utilizan las palabras reservadas new (para la creación) y delete (para liberar recursos).Podemos usar new de las siguientes formas:

_instancia = new nombre_de_clase ();
_instancia = new nombre_de_clase (parámetros);

«nombre_de_clase» hace referencia al nombre de una clase accesible por el script. Hay que tener en cuenta si la clase a la que hacemos referencia es hija de la clase desde la cual se hace la llamada a new. También si la clase pertenece
al mismo namespace o si es accesible a través de los directorios importados.La clase puede tener diferentes constructores _operator_new implementados. En función de cómo usemos el new, se invocará el que cumpla con el número de parámetros usado. Por ejemplo:

_enemy = new Enemy ("troll", 10);

Para eliminar una instancia de clase creada, utilizaremos:

delete _instancia;

Esto provocará que se ejecute el «_operator_delete» si está implementado en la clase de la instancia a eliminar. Este operador no usa parámetros nunca, y por tanto solo podemos definir uno por clase.

Herencia de clases

La herencia de clases permite crear jerarquía y encapsular comportamientos. Mediante este mecanismo, podemos asociar, extender y/o redefinir código además de propiedades.Ejemplo:Tenemos dos archivos, Enemy.pi y Slurp.pi. Ambos son scripts.

Archivo: Enemy.pi

class ACTOR
{
   properties:
      life = 100;
      damage = 5;

   function Init ()
   {
   }

   function Move ()
   {
   }
}

Archivo: Slurp.pi

class ACTOR implements Enemy
{
   properties:
      life = 80; // Redefine el valor del padre
      damage = 8; // Redefine el valor del padre
      salto_h = 12.0f; // Crea una nueva propiedad

      function Init ()
      {
         ::Init (); // LLama al Init del padre
      }

      virtual Move () 
      {
      }
}

Al declarar como virtual, siempre se llamará primero a la función Move del padre (Enemy::Move). Básicamente el virtual equivale a:

function Move () { ::Move(); }

Partials

Mediante este mecanismo podemos incrustar código de archivos script dentro de otro script. No es lo mismo que «implements», donde los métodos y propiedades forman parte de una clase. En un «partial», se incluye el código de otro script como si fuese código propio. Una similitud a esto sería la directiva de preprocesador #include.

Ejemplo:

class Base
{
    properties:
        id = 0;
        name = "";

    function GetID()
    {
        return id;
    }    
}

class Book partial Base
{
    properties:
        ref = "";
        content = "";

    function GetContent()
    {
        return content;
    }

    function GetID()
    {
        return ref;
    }
}

_a = new Book();

Ahora la instancia creada dispondrá tanto de funciones como de propiedades de Base como si fuesen propias, en el sentido de que si creásemos alguna propiedad con el mismo nombre en Book que en Base, tendríamos un error de compilación. En cuanto a las funciones, si en la clase Book creamos funciones que ya existen en
Base, estas se ignoran y siempre se usan las que se incluyan de Base. Nótese que no hay forma de invocar la función de Book y la de Base de ningún modo: para disponer de herencia hay que usar implements.

Máquina de estados

VR-SCRIPT permite trabajar también con una máquina de estados. Estos se declaran en el cuerpo de la clase como:

state "NAME"
{
}

Mediante la sentencia: _change(«»); cambiamos de un estado a otro. Los estados se ejecutan atómicamente; quiere decirse que todas las sentencias de un estado se ejecutan una detrás de otra dentro de la actualización de la lógica
del sistema.

Ejemplo:

class Enemy
{
    state "Quiet":
    {
        if (EnemyDistance (this, player) < 100.0f)
        {
            _change ("FindPlayer");
        }
    }
    state "FindPlayer":
    {
    }
}

Evaluación de expresiones al vuelo

Mediante _eval podemos evaluar expresiones en formato texto al vuelo.

a = 10;
b = _eval ("a + 10"); // esto hace que b valga 20.

Ejecución de código al vuelo

Mediante _run podemos ejecutar bloques en formato texto de código al vuelo.

_block = @@ if (a==5) print ("Es correcto!") else a=10; @@
_run (_block);

Sobrecarga de operadores

Son funciones que se pueden implementar y su uso va estrictamente relacionado con el tipo Pointer. El parámetro \ sería un tipo de dato (int, float, pointer, etc.). La función en teoría debería devolver el tipo requerido en la sobrecarga. Mediante el uso de new y delete es posible crear nuevos objetos y eliminarlos.

function _operator_new ()
function _operator_delete ()
function _operator_sizeof ()
function _operator_get (indice)
function _operator_set (indice, value)
function _operator_mod (v1, v2)
function _operator_mul (v1, v2)
function _operator_pow (v1, v2)
function _operator_div (v1, v2)
function _operator_add (v1, v2)
function _operator_sub (v1, v2)
function _operator_equal (v1, v2)
function _operator_notequal (v1, v2)
function _operator_great (v1, v2)
function _operator_less (v1, v2)
function _operator_greatequal (v1, v2)
function _operator_lessequal (v1, v2)
function _operator_logand (v1, v2)
function _operator_logor (v1, v2)
function _operator_lognot (v)
function _operator_not (v)
function _operator_and (v1, v2)
function _operator_xor (v1, v2)
function _operator_or (v1, v2)
function _operator_lshift (v)
function _operator_rshift (v)

Optimizaciones

Un acceso a una propiedad o variable global es un 25% más rápido que una llamada a función. El acceso a los elementos de una cadena es más lento que el acceso a los elementos de una lista.

cadena = "ABCDEF";
lista = [65, 66, 67, 68, 69, 70];
//... dentro de un bucle
a = cadena[1]; // devuelve 66
b = lista[1]; // devuelve 66, y es más rápido
//... fin del bucle

La condición de un bucle se evalúa en cada paso del bucle, y cada cálculo que hacemos dentro, como sizeof(value), se vuelve a calcular cada vez, así que es mejor moverlo fuera del bucle.El preprocesador es lento. Cuando se define una macro el procesador debe analizar todo el archivo, hacer conversiones de texto, etc., lo cual puede ralentizar la ejecución antes del Init. Es recomendable poner las macros/defines en un archivo pequeño.

Definición de funciones con número de parámetros indefinido

Podemos declarar una función que puede contener un numero de parámetros indefinido:

function Test (...)
{
}

A esta función la podemos llamar con el número de parámetros que queramos:

Test();
Test(5);
Test("hola", 6, "adios");

Para poder acceder a los parámetros desde la función usaremos _get indicando el índice del parámetro:

function Test (...)
{
    Print(_get(0));
}

Con _get(«\@») obtendremos el número de parámetros variables que se le pasan a la función. En el siguiente ejemplo, tenemos un parámetro fijo obligatorio «_a» y el resto serían opcionales:

function Test2 (_a, ...)
{
}

Si la función se invoca como:

Test2(5, 6, "hello"); 
_get("@")

La función _get(«\@») nos devolverá 2.

Instanceof

Con _instanceof podemos saber si un objeto es una instancia de una clase, y en qué grado o nivel. Por ejemplo, con la siguiente estructura de clases:

Archivo: Animal.pi

class Animal
{
    properties:
        A = 5;
}

Archivo: Mamifero.pi

class Mamifero implements Animal
{
    properties:
        B = "delfin";
}

Archivo: Perro.pi

class Perro implements Mamifero
{
    properties:
        C = "pastor alemán";
        function Ladra()
        {    
            // ...
        }
}

Podemos hacer lo siguiente:

A = new Animal();
M = new Mamifero();
P = new Perro();

_res = A._instanceof("Animal"); // _res valdrá 1
_res = M._instanceof("Animal"); // _res valdrá 2
_res = P._instanceof("Animal"); // _res valdrá 3
_res = A._instanceof("Perro"); // _res valdrá 0

Implements

Con la función _implements, podemos saber si un objeto implementa una función o si tiene alguna propiedad/constante que queramos averiguar. Usando el ejemplo anterior:

_res = P._implements("Ladra"); // _res = 1
_res = A._implements("Ladra"); // _res = 0
_res = P._implements("C"); // _res = 1
_res = P._implements("A");; // _res = 1
_res = M._implements("C");; // _res = 0
es_ESSpanish