Manual de usuario
Fco. Javier Bohórquez Ogalla
La plataforma OMI representa un sistema software desarrollado para la comunidad académica. Pretende ser una herramienta utilizada en el aprendizaje de los sistemas intérpretes y traductores modernos, además de cualquier otro basado en los conceptos sobre los que estos se construyen.
La plataforma OMI se constituye mediante los siguientes elementos:
Este manual describe al lenguaje, las funciones que integra, y las características del intérprete. Además detalla cómo se estructura el contenido dentro de la aplicación web y las herramientas que la componen.
Para usar el intérprete OMI es necesario disponer de un sistema GNU/Linux. El intreprete depende de una serie de bibliotecas de programación. Dependiendo de la instalación que se realice será necesario la instalación previa de estas o no.
Por otro lado para el correcto uso de la aplicación web se precisa de un navegador web con soporte HTML5 y JavaScript.
Se recomienda la descarga de la última versión estable del sistema software.
El proceso de compilación se vale de una serie de herramientas que lo simplifican y parametrizan, permitiendo la configuración del ejecutable obtenido.
Además se mantiene dependencia con las siguientes bibliotecas de programación:
Instalación de dependencias en una distribución Debian: language=bash
$PS1 apt-get install autoconf automake build-essential \ $PS2 libreadline-dev libboost-regex-dev
Para la compilación es necesario abrir una sesión de terminal de comandos en el directorio sobre el que se desea llevar a cabo el proceso. Se supone que el usuario tiene permisos suficientes en el sistema para llevar a cabo la instalación.
Descarga del código fuente de la versión $VERSION:
$PS1 wget http://wwww.omi-project.com/download/code/omi_$VERSION.tar.gz
Descompresión y desempaquetado del fichero:
$PS1 tar -xzvf omi_$VERSION.tar.gz $PS1 cd omi_$VERSION
Es posible establecer determinados aspectos del intérprete durante el proceso de compilación. Las opciones más significativas son las siguientes:
Para ver un listado completo de las opciones de configuración para la compilación:
$PS1 ./configure --help
Configurar el proceso de compilación con las opciones por defecto:
$PS1 ./configure
Durante la ejecución de este script se comprobará si se satisfacen las dependencias, y se creará los scripts necesarios para la compilación.
Para llevar a cabo la compilación:
$PS1 make
Una vez compilado con éxito es posible proceder con la instalación:
$PS1 make install
Para comprobar la integridad de la instalación:
$PS1 make check
Para limpiar el entorno de compilación:
$PS1 make clean
Para desinstalar el software:
$PS1 make unistall
Con una sesión de terminal y los permisos en el sistema suficientes es posible descargar e instalar el paquete .deb
Para descargar el paquete:
$PS1 wget http://wwww.omi-project.com/download/deb/omi_$VERSION.deb
Para instalar el paquete:
$PS1 dpkg -i omi_$VERSION.deb
Para desinstalar el paquete:
$PS1 dpkg -r omi
Para ejecutar el intérprete se utiliza el comando con el mismo nombre.
$PS1 omi
Ejecutar el comando sin argumento hará que el intérprete lea y procese la entrada estándar.
La sintaxis del comando es la siguiente:
El fichero será un documento escrito en el lenguaje OMI. La lista de argumentos serán datos accesibles desde el código fuente contenido en el fichero.
Las posibles opciones son:
Los argumentos facilitados serán accesible desde el código fuente mediante el array ``args'', donde la primera posición del array es el nombre del fichero y las siguientes los argumentos facilitados.
Se pone a disposición del usuario una hoja de manual que detalla el uso del comando.
$PS1 man omi
Con la instalación por defecto el intérprete es ubicado en la siguiente ruta:
Además los recursos, como por ejemplo las extensiones, serán ubicados en el siguiente directorio:
language=omi
Es posible delimitar un bloque de sentencias entre llaves. Muchos recursos del lenguaje operan o trabajan sobre un bloque de sentencias. Estas no necesitan acabar en ``;''.
Los cierre de bloque o fin de la entrada automáticamente implica un punto y coma.
En el modo interactivo no es necesario si la línea introducida sólo se compone de una sentencia.
<< "Hola mundo"; // Sentencia acabada
if (true) { << "Y un ejemplo"; } // Bloque de sentencias
<< "Soy un programa OMI"
En OMI es posible realizar un comentario de línea al estilo de C++ o de consola de comando UNIX.
x = 1; //x vale 1 y = 2; #y vale 2
Por otro lado, es posible escribir un bloque de una o varias líneas de comentarios delimitándolo mediante ``/*'' y ``*/''.
/*
x vale 1
y vale 2
*/
x = 1;
y = 2;
La ejecución del programa normalmente se realiza sentencia a sentencia de forma secuencial. Algunas sentencias y expresiones del lenguaje podrán cambiar este flujo.
La ejecución finaliza cuando se llega al final de este fichero o se resuelve una sentencia que produzca la salida. También es posible que se de algún error sintáctico o semántico
Un identificador debe comenzar por una letra (mayúscula o minúscula) o un subguión (_), seguidos de tantas letras, números o subguiones como se desee.
foo = 1; // foo identifica una variable que vale uno
~ bar () { // bar identifica una función que imprime "Hola mundo".
<< "Hola mundo";
}
OMI es sensible a mayúsculas, por lo que considera dos identificadores distintos si no mantienen la misma capitalización.
Un identificador no puede coincidir con una palabra reservada del lenguaje o se producirá un error sintáctico.
En OMI se nombran de igual forma las variables, funciones, clases... Es el contexto en el que se da un identificador el que determina el recurso que este nombra. Por regla general todos los operadores, funciones y demás construcciones del lenguaje toman, a no ser que se indique lo contrario, los identificadores como variables. Las excepciones a esta regla son:
OMI dispone de mecanismos para indicar explícitamente el tipo de recurso que es nombrado.
OMI es un lenguaje de programación que presenta un tipado dinámico, es decir, las variables se consideran del mismo tipo que al dato al que referencian. Así no es necesario declarar el tipo de dato que contendrán.
Los tipos de datos sobre los que opera OMI son:
Algunas funciones y operadores del lenguaje tratan tipos de datos concretos. A pesar de ello OMI es un lenguaje débilmente tipado, por lo que es posible usar valores de un tipo de dato en expresiones que requieran otro tipo de dato. Para ello se lleva a cabo una conversión de tipo de forma implícita.
a = true; // a presenta el valor verdadero b = false; // b presenta el valor falso
8#6
A pesar de que solo presenta un tipo de dato numérico ofrece mecanismos para operar con enteros y decimales, por ejemplo es posible obtener la parte entera de un número decimal.
Los números son expresados en OMI mediante cualquier conbinación de dígitos, separando la parte decimal mediante un punto si existiera. Además es
posible preceder la expresión de un signo ``+'' o ``-''.
a = 1; // a presenta el valor 1 b = -1; // b presenta el valor -1 c = +1; // c presenta el valor +1 d = 0.001; // d presenta el valor 0.001 e = -0.001; // e presenta el valor -0.001
En OMI es posible configurar cómo los tipos de datos numéricos son almacenados y tratados de forma interna. Así es posible alterar el valor numérico máximo que se puede representar
y la precisión. No obstante, esto sólo es posible en tiempo de compilación, por lo que se deberá usar la misma configuración para todos las ejecuciones.
8#6
En OMI las cadenas de caracteres son consideradas como un tipo de dato simple. Esto es debido a que no existe el tipo de dato carácter, y por tanto los datos de este tipo no puede ser descompuesto en datos de tipos más simples. De esta forma se considera al carácter simple como una cadena de un solo elemento.
Una cadena de caracteres queda delimitada entre comillas simples o comillas dobles.
a = "Una cadena de caracteres"; // a es una cadena definida entre " b = 'Y otra'; // b es una cadena definida entre '
8#6
En OMI un array es una estructura de datos que almacena valores de forma contigua. Los datos que guarda un array pueden ser de diferente tipo.
Se pueden dar dos tipos de arrays:
Los arrays secuenciales son accesibles mediante índices numéricos y de una forma posicional. Se definen mediante una secuencia de expresiones que representan
un valor. Estas serán separadas por el carácter ``,'' y delimitadas entre llaves ``'' y ``''. El primer elemento del array se corresponde con el índice numérico 0.
a = {0,1,2}; // a es un array que contiene los números 0, 1 y 2
/*
b es un array que contiene el numero 0, la
cadena "str" y otro array con los numeros 0 y 1
*/
b = {0,"str", { 0, 1 } };
En arrays asociativos cada elemento está referenciado mediante una clave, de forma que se puede utilizar esta para acceder al elemento dentro del array. En OMI las claves
de los arrays asociativos pueden ser cualquier expresión válida que represente un valor de tipo simple. Un array asociativo se define igual que uno secuencial salvo que los elementos que lo
componen son pares de expresiones correspondientes a la clave y al valor y separadas por el carácter ``:''.
/*
a es un array asociativo con las cadenas
'k0', 'k1' y 'k2' como claves. Sus valores
son 0, 1 y 2 respectivamente.
*/
a = {'k0' : 0, 'k1' : 1, 'k2' : 2};
/*
b es un array asociativo con los números 0 y
10, y la cadena "K" como claves. Sus valores son
el numero 0, la cadena "str" y otro array con
los numeros 0 y 1.
*/
b = { 10: 0, 0: "str", "K": { 0, 1 } };
9#7
El uso de las clases y los objetos es descrito en profundidad como una referencia del lenguaje (sec:class).
En OMI todo dato es considerado de tipo objeto, los otros tipos se ven como clases de objetos. Un dato, además de tener asociado un valor de un tipo concreto,
presenta una serie de acciones u operaciones que se pueden realizar con él y que están determinadas por su tipo. Por ejemplo sobre un dato array se puede realizar la operación
implode para obtener una cadena con todos los elementos del array separado por una subcadena separadora.
/*
A la variable "a" se le asigna
el resultado de ejecutar la operación
implode sobre el array.
*/
a = {"hola","mundo"}->implode(" ");
OMI permite al usuario definir, construir y utilizar sus propios objetos. Para ello se vale de las clases y de mecanismos para instanciar los objetos a partir de estas.
10#8
En OMI cualquier referencia sin valor se considera como nulo. Las variables no inicializadas, o las posiciones de arrays inexistentes tienen el valor nulo.
Es posible expresar un dato nulo mediante el literal ``null'' (o en mayúsculas ``NULL'').
a = null; // a no contiene valor
El uso de las funciones es descrito en detalle más adelante, en este manual, como una referencia del lenguaje (sec:class).
En OMI las funciones son consideradas tipos de datos, siendo posible operar sobre ellas. Así una función puede ser asignada a una variable, pasada como parámetro
o ser devuelta por otra función.
/*
a es una función que recibe un
parámetro y lo devuelve como valor
*/
a = ~(param){ return param; };
11#9
Con OMI se puede expresar cualquier expresión regular usando la sintaxis PERL. Para ello se ha de delimitar entre acento grave (` ).
/*
"exp" es la expresión regular correspondiente
al lenguaje descrito por cero o mas caracteres "a"
seguidos de dos caracteres "b"
*/
exp = `a*bb`;
El lenguaje dispone de funciones para crear y operar sobre expresiones regulares.
12#10
Normalmente no es posible usar un dato simple en lugar de uno compuesto. Las funciones y operaciones que esperan recibir como argumento un dato compuesto devolverán un error si no es así. No obstante un dato compuesto puede utilizarse como uno simple sin que se produzcan errores semánticos.
En el caso de los objetos, es posible definir métodos que serán invocados cuando sean utilizados como otro tipo de dato. Estos métodos son denominados métodos mágicos (sec:magic_method).
Un operador normalmente es de naturaleza binaria, presentando dos entradas, aunque existe operadores unitarios o ternarios. Además presenta una sintaxis y semántica propia.
foo = 1 + 1; // foo es el resultado de evaluar la operación suma
Los operadores pueden combinarse para formar expresiones más complejas. Para la evaluación se establece una precedencia de operadores implícita, que determina en qué orden
deben de resolverse. Es posible alterar la precedencia de operadores encerrando la expresión entre paréntesis.
foo = 2 * 8 + 3; // se evalúa la operación producto y luego la suma. bar = 2 * (8 + 3); //se evalúa la operación suma y luego el producto.
La mayoría de operadores que se pueden utilizar en OMI tienen una naturaleza matemática y se resuelven igual forma. No obstante también soporta otros operadores propios de los lenguajes de programación.
Al aplicarse un operador lógico se produce un resultado booleano derivado de la evaluación de una condición.
Los operadores lógicos son los siguientes:
foo = !true; // foo vale falso foo = true && false; // foo vale falso foo = true or false; // foo vale verdadero
Los operadores AND y OR son evaluados de izquierda a derecha en corto circuito. Se puede producir el resultado mediante una evaluación parcial, al conocerse de antemano el
valor que se obtendrá mediante la evaluación total. En este caso el resto de la expresión no será evaluada. Además el resultado siempre será el último operando en ser evaluado.
foo = null and 3 + 4; // foo vale null, evaluación parcial
foo = {} or "10"; // foo vale "10", evaluación total
foo = "10" and 3 + 4; // foo vale 7, evaluación total
foo = "5" or {}; // foo vale "5", evaluación parcial
19#17
Los operadores de comparación son los siguientes:
foo = 5 == 5; // foo vale verdadero foo = "5" == 5; // foo vale verdadero foo = 5 != 5; // foo vale falso foo = "3" < 4; // foo vale verdadero foo = "3" <= 4; // foo vale verdadero foo = 4 > 4; // foo vale falso foo = 4 >= 4; // foo vale verdadero foo = 5 === 5; // foo vale verdadero foo = "5" === 5; // foo vale falso foo = "5" !== 5; // foo vale verdadero
Para los tipos booleanos se considera que verdadero (true) es mayor que falso (false). Para los datos de tipo numéricos se toma su valor aritmético. Las cadenas de caracteres son ordenadas alfabéticamente.
Cuando se comparan datos de distintos tipos se realiza una conversión al tipo de dato más simple. Por ejemplo al comparar un dato numérico con un booleano se convertirá
el numérico a booleano.
foo = true <= 5; // foo vale verdadero foo = "5" == true; // foo vale verdadero foo = "word" < 4; // foo vale falso
Cuando se realiza una comparación de forma idéntica, además de comparar el valor de los datos, se comprueban los tipos.
19#17
Se corresponden con las operaciones aritméticas básicas que son la suma, la diferencia, el producto y la división, y aquellas derivadas de estas. El resultado de la operación será un número.
Los operadores aritméticos toman como operandos expresiones tratadas como numéricos. Todo dato que sea sometido a una operación aritmética será convertido a numérico.
Los operadores aritméticos soportados por OMI son:
foo = 1 + 1; // foo vale 2 foo = "5" + 5; // foo vale 10 foo = "word" + 4; // foo vale 8 foo = true + 10; // foo vale 11 foo = 4 - "2"; // foo vale 2 foo = 3 * 4; // foo vale 12 foo = 17 / 4; // foo vale 4.25 foo = 2.1 / "8"; // foo vale 0.2625 foo = 4 % 3; // foo vale 1 foo = 4 ^ 2; // foo vale 16
El resultado del operador módulo mantendrá el mismo signo que el operando que hace de dividendo.
33#31
foo = "ABC" . "DEF"; // foo vale "ABCDEF" foo = "ABCDE"[0]; // foo vale "A"
La concatenación se realiza después de que se determinen los números decimales presentes en la
expresión. Todo carácter punto no suponga un separador decimal será considerado un operador
de concatenación.
foo = "AB".2.1.1."CD"; // foo vale "AB2.11CD" foo = "AB".2.1.1.1."CD"; // foo vale "AB2.11.1CD"
El operador de acceso a posición recibe un índice que indica el elemento al que acceder dentro de la cadena. Si la posición indicada no existe se toma el valor nulo. El índice siempre será considerado como un dato numérico.
El operador de acceso también puede ser utilizado para escribir en una determinada posición de la cadena.
No obstante, en este caso, debe existir un elemento en la posición indicada o se producirá un error.
foo = "ABCD"; // foo vale "ABCD" foo[0] = "E"; // foo vale "EBCD" foo[0] = "FG"; // foo vale "FGBCD" foo[10] = "X"; // se produce un error.
No es posible utilizar el operador de acceso para escribir en una determinada posición de una cadena constante.
"ABCD"[0] = "X"; // se produce un error.
El operador de acceso es utilizado para otros tipos de datos como los arrays.
36#34
En OMI se lleva a cabo una asignación destructiva. Cuando una expresión es asignada, por ejemplo a una variable, el valor de esta última cambia, indistintamente de lo que contenía
anteriormente.
foo = 1; // foo vale 1 foo = 2; // foo a cambiado al valor 2 bar = foo; // bar vale el valor actual de foo que es 2 foo = 10; // foo a cambiado al valor 10 y bar sigue valiendo 2
OMI, como otros lenguajes, soporta una serie de operadores que combinan una operación determinada y una asignación. Estos simplifican muchas expresiones.
foo = 1; // foo vale 1 foo += 2; // foo a cambiado al valor 3 foo -= 4; // foo a cambiado al valor -1 foo *= -9; // foo a cambiado al valor 9 foo /= 3; // foo a cambiado a 3 foo ^= 3; // foo a cambiado a 27 foo %= 2; // foo a cambiado a 1 foo .= "F"; // foo a cambiado a "1F"
43#41
Se distinguen entre si, además de que si el valor es incrementado o decrementado, en el momento en el momento en el que se tomará el valor. Pudiendo aplicarse la operación antes o después de evaluar el resto de la expresión.
foo = 1; // foo vale 1 bar = ++foo; // bar vale 2 y foo vale 2 bar = foo++; // bar vale 2 y foo vale 3 bar = --foo; // bar vale 2 y foo vale 2 bar = foo--; // bar vale 2 y foo vale 1
48#46
foo = bool 4; // foo vale el booleano true foo = bool 0; // foo vale el booleano false foo = int "4.5"; // foo vale el numérico 4 foo = int "AA"; // foo vale el numérico 2 foo = float "4.5"; // foo vale el numérico 4.5 foo = string 55; // foo vale la cadena "55" foo = string true; // foo vale la cadena "1"
A pesar de que OMI soporta la conversión a enteros o flotantes, internamente estos se representan de la misma forma. Por lo que se consideran del mismo tipo.
53#51
foo = {"A","B"}; // foo es un array que contiene las cadenas "A" y "B"
bar = {"A","B"}[0]; // bar vale "A"
bar = foo[1]; // bar vale "B"
bar = size foo; // bar vale 2
foo[] = "C"; // foo es un array que contiene las cadenas "A", "B" y "C"
bar = size foo; // bar vale 3
foo = { "k0": "v0", "k1": "v1" }; // foo es un array asociativo con dos claves
bar = { "k0": "v0", "k1": "v1" }["k0"]; // bar vale "v0"
bar = foo["k1"]; // bar vale "v1"
bar = size foo; // bar vale 2
foo["k2"] = "v2"; // foo es un array asociativo con tres claves
foo[] = "v3"; // foo es un array asociativo con cuatro claves
Con el operador de acceso a posición es posible acceder a un determinado elemento dentro del array. Para ello se debe facilitar el índice o la clave a la que se desea acceder.
El operador de acceso a posición en array no realiza ninguna conversión de tipos sobre el índice o clave. Las claves son comparadas con el operador de igualdad (sec:op_cmp).
Si se accede mediante un índice o clave que no existe en el array se toma el valor nulo.
El operador de acceso a última posición por si solo toma el valor nulo, pero puede ser utilizado para añadir elementos al final del array.
No es posible asignar a posiciones de un array constante.
{1,2}[0] = 10; // se produce un error
{"key":"val"}["key"] = "other"; // se produce un error
{1,2}[] = 10; // se produce un error
El operador de acceso es utilizado en otros tipos de datos como las cadenas de caracteres.
57#55
OMI es un lenguaje de programación orientado a objetos, por lo que pone a
disposición del usuario un conjunto de operadores relacionados con estos.
El uso de las clases y objetos quedan descritos en la sección correspondiente (sec:class).
61#59
OMI define una serie de operadores que son aplicables a funciones.
El uso de las funciones queda descritos en la sección correspondiente (sec:function).
65#63
foo = isnull null; // foo es true
foo = isnull 4; //foo es false
foo = isbool true; //foo es true
foo = isbool false; //foo es true
foo = isbool 5; //foo es false
foo = isnum 4; // foo es true
foo = isnum "4"; // foo es false
foo = isnum 4.6; // foo es true
foo = isstring "4"; // foo es true
foo = isstring 4; // foo es false
foo = isarray {}; // foo es true
foo = isarray {1,2}; // foo es true
foo = isarray "{1,2}"; // foo es false
foo = isobject new Class1 (); // foo es true
foo = isobject 5; // foo es false
72#70
Los operadores condicionales evalúan una expresión que hace de condición, y en función del resultado obtenido realizan una determinada operación.
En el operador ternario si la expresión de condición se cumple se toma el valor del operando precedido por ``?'', en caso contrario se toma el del último, que es precedido por ``:''.
foo = (4 == 4)?"OP1":"OP2"; // foo vale "OP1" foo = (4 != 4)?"OP1":"OP2"; // foo vale "OP2" foo = 5?"OP1":"OP2"; // foo vale "OP1"
Existe distintas formas del operador ternario además de la descrita. El operador ternario simplificado en valor verdadero omite el operando que se devolverá en el caso de que la condición
sea cierta, en este caso se devolverá el valor de la expresión de condición. Por otro lado existe el ternario simplificado en valor falso, que omite el último operando
y que tomará el valor nulo.
foo = 5?:"OP2"; // foo vale 5 foo = 0?:"OP2"; // foo vale "OP2" foo = "COND"?"OP1"; // foo vale "OP1" foo = ""?"OP1"; // foo vale null
La fusión de nulos opera sobre una lista de operandos. El operador comprueba los elementos secuencialmente y devuelve el primero que no sea nulo o nulo si todos los son.
foo = [[ null, null, 4 ]]; // foo vale 4 foo = [[ null, 4, 5 ]]; // foo vale 4 foo = [[ null, null, null ]]; // foo vale null
75#73
El operador de entrada estándar tiene como operando una expresión variable. Este se encarga de leer un flujo de caracteres de
la entrada estándar hasta que lee un carácter fin de línea. El conjunto de caracteres leídos es asignado a la variable como
una cadena.
>> foo; // Se lee de la entrada estándar hasta un carácter fin de linea y se introduce en foo input foo; // Igual que el caso anterior
El operador de entrada tiene una segunda forma en la que se puede indicar una cadena de caracteres
que se mostrará como prompt.
/*
Se lee de la entrada estándar hasta un carácter fin de linea
y se introduce en foo. Para indicar que se espera una entrada
se utiliza la cadena "Example:"
*/
>>["Example:"] foo;
Los operadores de salida estándar se encargan de escribir en esta una cadena de caracteres.
Estos operadores se diferencian entre si, además de en su forma léxica, en si
escriben un carácter de fin de línea automático o no.
echo "Hola mundo"; // Escribe la cadena "Hola mundo" en la salida estándar <. "Hola mundo"; // Escribe la cadena "Hola mundo" en la salida estándar << "Hola mundo"; // Escribe la cadena "Hola mundo" en la salida estándar y un salto de linea
Es posible utilizar los operadores 78#76 y 79#77 para concatenar un flujo de cadenas de caracteres en la
salida estándar. En el caso del operador 78#76 se resolverá como el operador de concatenación de cadenas
34#32, introduciendo las cadenas una seguida a la otra. Por otro lado, usar el operador 79#77 implica que se
va a concatenar un salto de línea antes que la cadena
echo "Hola " . "mundo"; // Escribe la cadena "Hola mundo" en la salida estándar echo "Hola " <. "mundo"; // Escribe la cadena "Hola mundo" en la salida estándar echo "Hola " << "mundo"; // Escribe la cadena "Hola \mundo" en la salida estándar <. "Hola " . "mundo"; // Escribe la cadena "Hola mundo" en la salida estándar <. "Hola " <. "mundo"; // Escribe la cadena "Hola mundo" en la salida estándar <. "Hola " << "mundo"; // Escribe la cadena "Hola \mundo" en la salida estándar << "Hola " . "mundo"; // Escribe la cadena "Hola mundo\n" en la salida estándar << "Hola " <. "mundo"; // Escribe la cadena "Hola mundo\n" en la salida estándar << "Hola " << "mundo"; // Escribe la cadena "Hola \mundo\n" en la salida estándar
43#41
Una expresión puede estar compuesta por varios operadores, se establece pues prioridades entre estos que determinan cómo ha de resolverse la expresión.
Los operadores quedan ordenados en el siguiente listado de más prioritarios a menos:
Los operadores en el mismo nivel de prioridad son resueltos mediante una asociación desde la izquierda, excepto los operadores de asignación que son asociativos desde la derecha.
Una sentencia está formada por un identificador seguido del carácter ':' y la sentencia etiquetada.
label: << "Sentencia";
Las etiquetas normalmente son referenciadas por la sentencia de control 87#85 la cual cambia el flujo de ejecución a la sentencia etiquetada.
Normalmente se desaconseja el uso de etiquetas y sentencias 87#85 dado que dificultan la legibilidad del código. No obstante OMI soporta este mecanismo de control.
88#86
Las sentencias de control son construcciones del lenguaje que permiten alterar el flujo de ejecución del programa. Son muy comunes y utilizadas en los lenguajes de programación.
Se clasifican según su naturaleza, así es posible ver sentencias de control condicionales, iterativas, de salto, inclusivas o de excepción.
Muchas sentencias de control están formadas por bloques de sentencias cuya ejecución controlan. Estos bloques pueden estar formados por una sola sentencia.
La sentencia condicional
89#87 está formada por una expresión de condición, además de dos bloques de sentencias que serán ejecutadas en función la condición sea evaluada
como verdadera o falsa.
>>["Dime el valor de foo:"] foo;
if ( foo < 10) {
<< foo << " es menor que 10"; // Se ejecuta si la condición es verdadera
}
else {
<< foo << " no es menor que 10"; // Se ejecuta si la condición es falsa
}
Si el caso de evaluación negativa no implica la ejecución de ninguna sentencia, el bloque 90#88 puede omitirse.
>>["Dime el valor de foo:"] foo;
if ( foo < 10) {
<< foo << " es menor que 10"; // Se ejecuta si la condición es verdadera
}
Otra forma de esta sentencia condicional es
91#89, en donde se anidan varias
sentencias de este tipo. Se pueden anidar tantos 92#90 como sea necesario.
>>["Dime el valor de foo:"] foo;
if ( foo < 10) {
<< foo << " es menor que 10"; // La primera condición es verdadera
}
elif (foo == 10) {
<< foo << " es igual que 10"; // La primera es falsa y la segunda verdadera
}
else {
<< foo << " es mayor que 10"; // Todas las condiciones son falsas
}
Se compone de un dato a comparar y un bloque de sentencias etiquetadas con casos. Los casos están formados por una expresión que
será comparada con el dato. La ejecución pasará a la primera sentencia etiquetada
con el caso que sea igual al dato comparado, procediéndose a ejecutar todas las sentencias siguientes.
>>["Sentencia inicial:"] foo;
switch (foo) {
case 0:
<< "Sentencia 0"; // Se ejecuta si foo es 0
case 1:
<< "Sentencia 1"; // Se ejecuta si foo es 0 o 1
case 2:
<< "Sentencia 2"; // Se ejecuta si foo es 0, 1 o 2
}
Es una práctica muy común, para omitir la ejecución de algunas sentencias, el utilizar la sentencia 94#92, la cual finaliza la
ejecución del bloque de sentencias actual.
>>["Sentencia inicial:"] foo;
switch (foo) {
case 0:
<< "Sentencia 0"; // Se ejecuta si foo es 0
break;
case 1:
<< "Sentencia 1"; // Se ejecuta si foo es 1
break;
case 2:
<< "Sentencia 2"; // Se ejecuta si foo es 2
}
Si ningún caso es igual al dato comparado no se ejecuta ninguna sentencia del bloque, a menos que se encuentre
la etiqueta 95#93.
>>["Sentencia a ejecutar:"] foo;
switch (foo) {
case 0:
<< "Sentencia 0"; // Se ejecuta si foo es 0
break;
case 1:
<< "Sentencia 1"; // Se ejecuta si foo es 1
break;
case 2:
<< "Sentencia 2"; // Se ejecuta si foo es 2
break;
default:
<< "Sentencia por defecto"; // Se ejecuta si foo no es 0, 1 ni 2
}
96#94
La sentencia 97#95 es una estructura de control iterativa que permite la ejecución de un bloque de
sentencias repetidamente mientras que se cumpla una determinada expresión de condición. Se compone pues de una expresión que será evaluada como
un dato booleano y un bloque de sentencias.
/*
Imprime la cadena
"Sentencia" 10 veces.
*/
foo = 0;
while (foo < 10) {
<< "Sentencia";
foo++;
}
La expresión de condición es evaluada al comienzo de cada ejecución del bloque de sentencias, por lo que si alguna sentencia vuelve la condición falsa se seguirá con la ejecución del bloque hasta la próxima iteración.
La sentencia
98#96 es una estructura de control iterativa que permite la ejecución de un bloque de
sentencias repetidamente mientras que se cumpla una determinada expresión de condición, y al menos una vez. Se compone pues de una expresión que será evaluada como
un dato booleano y un bloque de sentencias.
/*
Imprime la cadena
"Sentencia" 10 veces.
*/
foo = 0;
do {
<< "Sentencia";
foo++;
} while (foo < 10);
A diferencia de la sentencia 98#96 la condición es evaluada al final de la ejecución del bloque de sentencias por lo que se asegura que este se ejecutará al menos una vez. Al igual que 98#96 si alguna sentencia vuelve falsa la condición se proseguirá con la ejecución hasta que se produzca la evaluación.
La sentencia 99#97 es una estructura de control iterativa que permite la ejecución de un bloque de
sentencias repetidamente mientras que se cumpla una determinada expresión de condición. La sentencia 99#97 conlleva
la ejecución de la expresión de inicialización al inicio de la sentencia, y de una expresión de iteración al final de cada ejecución del
bloque.
/*
Imprime la cadena
"Sentencia" 10 veces.
*/
for (foo = 0; foo < 10; ++foo){
<< "Sentencia";
}
Es OMI es necesario especificar todas las expresiones que conforman la sentencia 99#97, si alguno es omitido se producirá un error sintáctico.
OMI ofrece unas formas de sentencias 99#97 que permiten ejecutar un bloque de sentencias para cada elemento contenido
en una determinada expresión. Para ello se debe especificar la expresión a recorrer, el símbolo variable con el
que se referenciará al elemento actual y el bloque de sentencias que se ejecutará para cada elemento contenido
en la expresión.
/*
Imprime la cadena
"Sentencia" y su índice
10 veces.
*/
for ( 10 as i ){
<< "Sentencia " . i;
}
/*
Imprime la cadena
"Sentencia" y su índice
10 veces.
*/
for ( i in 10 ){
<< "Sentencia " . i;
}
La diferencias entre 100#98 y 101#99 son puramente sintácticas, siendo el orden en que se especifican la expresión conjunto y el símbolo variable su principal diferencia.
El comportamiento de estas estructuras cambia en función el tipo de dato de la expresión conjunto.
Es posible especificar un par de símbolos variables separados por el carácter ``:''. Esto hará que si el elemento actual en el recorrido tiene una clave que lo referencia esta se guarde en
el primero y el valor en el segundo. Si no tuviera clave solo se asignará el valor al segundo símbolo variable.
/*
Imprime todas las
claves y valores del array
*/
array = {'k0':'v0', 'k1':'v1', 'k2':'v2'};
for ( array as key:value ){
<< key . " => " . value;
}
OMI ofrece una estructura de control iterativa cuya sintaxis ha sido simplificada, su funcionalidad extendida. La sentencia de control de iteración ágil funciona igual que una sentencia 101#99, pero sin que se produzca una asignación de cada elemento a un símbolo variable. En su lugar se puede acceder al elemento en curso mediante un operador de acceso especial.
La sentencia de iteración ágil se compone de una expresión a recorrer y de un bloque de sentencias que se ejecutará para cada elemento en la expresión.
/*
Imprime la cadena
"Sentencia" 10 veces.
*/
$(10){
<< "Sentencia";
}
Durante la ejecución del bloque de sentencias es posible acceder al elemento actual mediante el operador ``$''.
/*
Imprime la cadena
"Sentencia" y su índice
10 veces.
*/
$(10){
<< "Sentencia " . $;
}
Es posible anidar dos o más sentencias de iteración ágil y acceder al índice de cada una de ellas, añadiendo una expresión que se corresponde con el nivel de
anidamiento tras el operador ``$'' y delimitada entre llaves.
/*
Imprime las tablas de multiplicar del
0 al 9.
*/
$(10){ // Nivel 0
<< "Tabla de multiplicar del " . ${0};
$(10) { // Nivel 1
<< ${0} . " x " . ${1} . " = " . (${0} * ${1});
}
}
Si el operador de acceso no presenta índice este se corresponderá con el nivel actual.
/*
Imprime el valor dado
hasta que este es 0.
*/
while(true){
>>["Dame un valor (0 para salir)"] foo;
if (foo === "0")
break;
<< foo;
}
<< "Finalizando";
Es posible salir de dos bloques iterativos que se encuentren anidados, indicando tras 94#92 cuantos satos se desean realizar.
/*
Imprime el valor dado
hasta que este es 0.
Imprimiendo cuantas
solicitudes se han
realizado cada 10 veces
*/
count = 0;
while (true) {
i = 0;
while(i < 10){
>>["Dame un valor (0 para salir)"] foo;
count ++;
if (foo === "0")
break 2;
<< foo;
i ++;
}
<< "Se han solicitado " . count . " valores ";
}
<< "Finalizando";
La sentencia 102#100 permite terminar la ejecución actual de un bloque de sentencias correspondientes a una estructura iterativa. De esta forma se salta a la comprobación y
ejecución (si fuera el caso) de la siguiente iteración.
/*
Imprime el valor dado,
excepto si este es "jump",
hasta que es 0.
*/
while(true){
>>["Dame un valor (0 para salir, jump para saltar)"] foo;
if (foo === "0")
break;
if (foo === "jump")
continue;
<< foo;
}
<< "Finalizando";
Es posible utilizar la sentencia 102#100 para saltar a la siguiente iteración de una estructura iterativa concreta de varias anidadas. Para ello tras el literal
se debe indicar una expresión cuyo valor sea el número de bloques anidados que se desea saltar.
/*
Imprime el valor dado hasta que este es 0.
Imprimiendo cuantas solicitudes se han realizado cada 10 veces.
Es posible forzar el reinicio del contador mediante el valor "restart"
*/
count = 0;
while (true) {
i = 0;
while(i < 10){
>>["Dame un valor (0 para salir, restart para reiniciar contador)"] foo;
count ++;
if (foo === "0")
break 2;
if (foo === "restart"){
<< "Reiniciando contador ";
count = 0;
continue 2;
}
<< foo;
i ++;
}
<< "Se han solicitado " << count << " valores ";
}
<< "Finalizando";
/*
Se imprime la cadena
"Sentencia" indefinidamente
*/
label: << "Sentencia";
goto label;
En general se desaconseja el uso de etiquetas y sentencias 87#85 dado que dificultan la legibilidad del código.
Lo habitual es que la sentencia 87#85 se encuentre condicionada para que el salto no se ejecute siempre.
/*
Se imprime la cadena
"Sentencia" 10 veces
*/
foo = 0;
label: << "Sentencia " . foo;
if (foo < 10){
foo++;
goto label;
}
103#101
/*
fichero1.omi
Se imprime el contenido de este fichero
y se incluye el fichero 2
*/
<< "Contenido fichero 1";
include "fichero2.omi";
/*
fichero2.omi
Se imprime el contenido de este fichero
*/
<< "Contenido fichero 2";
Si la cadena facilitada como expresión no se corresponde con un fichero del sistema se producirá un error y finalizará la ejecución del script.
La sentencia 105#103 permite finalizar la ejecución del script en cualquier parte del mismo.
/*
Finaliza el script sin llegar
a ejecuta la ultima sentencia
*/
<< "SENTENCIA EJECUTADA";
exit;
<< "SENTENCIA NO EJECUTADA";
A diferencia de otros lenguajes de programación en OMI no existe una forma de finalizar el script devolviendo un estado de error.
La sentencia 106#104 permite establecer un objeto como contexto dentro de un bloque de sentencias. Se forma mediante una expresión correspondiente al objeto y un bloque de sentencias que será ejecutada con el objeto como contexto.
Cuando se establece un objeto como contexto todas las funciones que se llamen, y que no se encuentren definidas, serán
llamadas como métodos del objeto.
class Foo {
~ identity () {
<< "Soy Foo";
}
}
foo = new Foo ();
with (foo) {
identity(); // Llama al método identity del objeto foo
}
La sentencia 107#105 permite delimitar mediante un bloque de sentencias un comportamiento en el que se pueden dar excepciones. Además, mediante otro bloque de sentencias y un símbolo variable, se puede especificar cómo serán tratadas las excepciones que se den.
A diferencia de otros lenguajes de programación OMI solo permite un bloque de
sentencias 108#106. No comprueba la clase de la excepción que se ha dado, esto recae
en resposabilidad del programador.
class Exc {
var = null;
~ Exc (num) {
this->var = num;
}
~ printError () {
<< "Error " . this->var;
}
}
try {
>>["Dame un valor:"] foo;
if ( foo === "" ){
throw new Exc (404);
}
<< foo;
}catch (exc) {
exc->printError ();
}
109#107
La sentencia 110#108 permite lanzar una excepción que puede ser atrapada por una
sentencia
107#105. Esta sentencia se construye mediante una expresión cuyo
valor será asociado al símbolo variable del bloque de sentencias 108#106.
class Exc {
var = null;
~ Exc (num) {
this->var = num;
}
~ printError () {
<< "Error " << this->var;
}
}
try {
>>["Dame un valor:"] foo;
if ( foo === "" ){
throw new Exc (404);
}
<< foo;
}catch (exc) {
exc->printError ();
}
Si una excepción lanzada con 110#108 no sucede dentro de una sentencia 111#109 controlada se producirá un error y se detendrá la ejecución del script.
112#110
La sentencia 113#111 hace que la ejecución del programa se pare un número de segundos dados.
<< "Antes de parar"; sleep (10); << "Tras 10 segundos";
foo = 10;
typeof foo; // Imprime Arithmetic
foo = "STR";
typeof foo; // Imprime String
foo = {1,2};
typeof foo; // Imprime Array(2)
datInfo 5; // Imprime ptr(0x1d72e00), type(Arithmetic: 5), refs(0) datInfo "A"; // Imprime ptr(0x1d72f70), type(String: A), refs(0) foo = 10; datInfo foo; // ptr(0x1d73f00), type(Arithmetic: 10), refs(1) bar = foo; datInfo bar; // ptr(0x1d73f00), type(Arithmetic: 10), refs(2)
La sentencia 115#113 también admite la forma 116#114.
El valor de una variable puede cambiar durante la ejecución del programa. Para alterar el contenido de una variable lo habitual es utilizar el operador de asignación. En OMI la asignación de una variable es destructiva, es decir, la variable modifica su valor sobrescribiendo el valor anterior.
Las variables suponen un recurso esencial para los lenguajes de programación imperativos, ya que son el mecanismo básico para guardar el estado del sistema. Las variables en matemáticas forman parte de definiciones y una vez atribuido su valor este no variará, esto difiere del concepto de variable que presentan los lenguajes de programación imperativos.
OMI es un lenguaje de programación de tipado dinámico, y por tanto, el tipo de dato de una variable está determinado por el valor actual de la misma, y no se encuentra asociado a la variable. En un momento dado una variable podría contener un valor entero, y en otro una cadena de caracteres.
En OMI el tipo de dato del valor almacenado en una variable es desconocido hasta que es obtenido en la resolución de una expresión. Los operadores, funciones y demás recursos del lenguaje usan el valor para operar, con independencia de la variable que lo contuviera.
En OMI no es necesario declarar ni inicializar una variable antes de su uso. Si una variable sin valor es usada en una expresión se toma el valor nulo.
Internamente OMI representa las variables como referencias a los valores, más que como contenedores de estos. En OMI no es necesario llevar una gestión los datos que han sido creados y asignados a las variables de forma dinámica, un subsistema denominado recolector de basura será el encargado de esta tarea.
Las variables deben ser nombradas mediante un identificador válido, y no deben coincidir con ninguna palabra reservada.
Las variables en OMI pueden presentar un ámbito local y global. Una variable puede ser local al flujo principal o a una función o método. Por defecto las variables son locales.
Una variable local solo puede ser accesible desde donde se utilizó inicialmente. Si en otro lugar se hace uso del mismo identificador se consideran variables distintas.
Las variables globales son accesibles desde cualquier parte de la aplicación. Para usar una variable como global debe ser declarada como tal con el literal 117#115.
foo = 10; // foo es una variable local al flujo principal
global bar; // bar es una variable global.
bar = 20;
~ func () {
foo = 30; // foo es una variable local a la función func
bar = 40; // Se asigna a la variable global bar
}
118#116
Es posible obtener una referencia anteponiendo el símbolo ``119#117'' ante el símbolo variable.
foo = 10; bar = &foo; // Se obtiene una referencia de foo << foo . " - " . bar; // Imprime 10 - 10 bar = 20; << foo . " - " . bar; // Imprime 20 - 20
En OMI es posible obtener una referencia de cualquier expresión que sea variable, y acceder su valor de forma indirecta. Así por
ejemplo se puede obtener una referencia a una posición de un array, a un atributo de un objeto, etc.
foo = {{1,2}, 3};
bar = &foo[0][1];
bar = 4;
<< foo[0][1];
No es posible obtener una referencia a una posición dentro de una cadena, esto es debido a que en OMI las cadenas de caracteres son un tipo de dato simple.
En su lugar se copiará el valor del carácter en la posición como otra cadena, por lo que la referencia no tendrá efecto.
foo = "ABCD"; bar = &foo[0]; bar = "Z"; << bar; // Imprime "Z" << foo; // Imprime "ABCD". La referencia no tiene efecto.
Sintácticamente es posible obtener una referencia de un valor constante, no obstante esto hará que se tome el valor en si, sin que tenga efecto alguno.
Las referencias pueden ser utilizadas como un dato, por lo que pueden ser pasadas como parámetro. En este caso cualquier acceso al parámetro dentro de la función
se hará sobre las variables a las que referencia.
~ inc (param) {
param += 1;
}
a = 20;
inc(&a);
<< a; // Imprime 21
Es posible hacer que algunos parámetros de una función sean pasados siempre por referencia. Para ello se antepone 119#117 al parámetro en la definición de la función. Ver paso de parámetros por referencia (sec:func_ref).
Las funciones y métodos pueden devolver referencias. De esta forma si una llamada a función es asignada se hará la operación sobre la referencia devuelta,
por lo que cualquier acceso se hará sobre el símbolo variable a la que esta apunta. Si la referencia es a un símbolo variable de ámbito local, el cual es liberado al terminar la llamada,
el valor será copiado.
class example {
private attr = null;
~ example (attr) {
this->attr = attr;
}
~ getRef () {
return &this->attr;
}
~ printAttr () {
<< this->attr;
}
}
foo = new example (20);
foo->printAttr(); // Imprime 20
<< foo->attr; // Error acceso a elemento privado
bar = foo->getRef(); // bar es una referencia a foo->attr
bar = 40;
foo->printAttr(); // Imprime 40
Una función se define normalmente mediante un identificador, una lista de parámetros (delimitados por paréntesis y separados por coma) y un bloque de sentencias. La
definición de una función debe empezar por el carácter ``120#118'' o la palabra reservada 121#119.
~ func1 (param1, param2) {
<< "Soy fun1 con dos parametros:";
<< "param1 => " . param1;
<< "param2 => " . param2;
}
function func2 (param1, param2) {
<< "Soy func2 con dos parametros:";
<< "param1 => " . param1;
<< "param2 => " . param2;
}
Los parámetros de la función suponen un mecanismo para que esta pueda recibir datos con los que operar y realizar su tarea. Los parámetros son variables que pueden ser utilizadas en el bloque de sentencias y cuyos valores serán dado cuando la función es llamada.
Las funciones pueden ser llamadas desde cualquier parte del código, lo que supondrá la ejecución del bloque
de sentencias. Para realizar una llamada a una función se utilizará el nombre de la función seguido de los
valores que serán atribuidos a los parámetros, delimitados por paréntesis y separados por coma. La atribución
de valores a los parámetros se hace de forma posicional.
~ func1 (param1, param2) {
<< "Soy fun1 con dos parametros:";
<< "param1 => " . param1;
<< "param2 => " . param2;
}
func1 ("valor1", "valor2");
En una llamada los parámetros son pasados, a no ser que se indique lo contrario, por valor. Esto quiere decir
que los valores son copiados a los parámetros, lo que implica que cualquier modificación de estos solo tendrá efecto en el bloque
que define la función.
~ func1 (foo) {
foo = 20;
<< foo; // Imprime 20
}
foo = 40;
func1 (foo);
<< foo; // Imprime 40
Una función puede devolver un valor que será atribuido a la llamada. Para ello se hace uso de la sentencia 122#120. Esta sentencia, que puede formar parte del bloque de sentencias de una función, esta formada por el valor que será devuelto y atribuido a la llamada. La ejecución de la sentencia 122#120 implica la finalización de la función.
La llamada a un función es considerada un operador y puede formar parte de expresiones. El
valor de la llamada es dado por la ejecución de la sentencia 122#120 en el bloque de sentencias
de esta. Si la ejecución de la función termina sin una sentencia 122#120 se tomará el valor nulo.
~ sum (op1, op2) {
return op1 + op2;
}
<< sum(1,2) * 4; // Imprime 12
Las llamadas a función son resueltas antes que cualquier otro operador, ya que presentan el mayor nivel de prioridad.
Si una función es llamada sin que esta se encuentre definida se producirá un error semántico. También se producirá un error si el número de valores facilitados como parámetros en la llamada no se corresponden con la definición de la función. Cabe destacar que las definiciones de funciones son ejecutadas de forma secuencial dentro del flujo del programa, por lo que realizar un llamada de un función que se encuentre definida en sentencias posteriores producirá un error como si esta no se encontrara definida, por ello es buena práctica disponer las definiciones de funciones al comienzo del programa.
Los nombres de las funciones deben seguir las reglas normales de uso de identificadores, y no corresponderse con ninguna palabra reservada.
Si dos o más definiciones de función comparten el mismo identificador la última definición interpretada será la que se corresponda
con dicho identificador.
~ func () {
<< "FUNC 1";
}
~ func () {
<< "FUNC 2";
}
func(); // Imprime "FUNC 2"
A pesar de ser considerado un lenguaje imperativo, OMI presenta algunos recursos y mecanismos propios de la programación funcional. En las subsecciones siguientes se presentan los recursos y mecanismos que pueden ser utilizados y aplicados a las funciones.
En una llamada es obligatorio dar el valor de todos los parámetros excepto los que tienen definido un valor por defecto. Dado que la atribución de valores a los parámetros se hace de forma posicional, los parámetros con valores por defecto serán los últimos para que puedan omitirse en la llamada.
En la definición de una función se puede especificar que determinados parámetros tendrán valores por defecto siguiendo estos mediante el
signo igual y el valor que tendrán.
~ sum (op1, op2 = 4) {
return op1 + op2;
}
<< sum(1,2) * 4; // Imprime 12
<< sum(1) * 4; // Imprime 20
Es posible definir funciones donde algunos parámetros sean pasado como referencia en las llamadas. Esto es que si en la llamada se facilita un símbolo variable como valor de dicho parámetro, cualquier modificación realizada en el mismo será aplicado a la variable fuera de la función.
En la definición de una función se puede indicar que un determinado parámetro será pasado por referencia anteponiendo
el símbolo ``SPMamp;'' a dicho parámetro.
~ func1 (&foo) {
foo = 20;
<< foo; // Imprime 20
}
foo = 40;
func1 (foo);
<< foo; // Imprime 20
Si un valor constante es dado como parámetro por referencias se producirá un error.
En OMI las funciones son consideradas tipos de datos, por lo que es posible asignarlas a variables, operar sobre ellas, etc. Sin embargo, no se puede realizar una conversión de una función a otro tipo de dato y viceversa, por lo que la mayoría de operadores producirán un error al resolverse si operan con una función y otro tipo de dato.
Es posible utilizar la definición de una función como expresión en una operación de asignación.
foo = ~ sum (param1, param2) { return param1 + param2; };
<< foo (4, 5); // Imprime 9
((x < 10)?~(){<<"A";}:~(){<<"B"})(); // Si x < 10 imprime "A", si no imprime "B"
En muchos casos es necesario indicar explícitamente que el objeto que se desea utilizar es una
función ya definida, y no una variable u otro elemento. Para ello se antepone al identificador
de la función el par de símbolos ``123#121''.
~ foo () {
return "FUNCIÓN";
}
foo = "VARIABLE";
bar = foo; // bar vale "VARIABLE";
bar = ~&foo; // var vale la función que devuelve "FUNCIÓN"
Por regla general todos los operadores toman, a no ser que se indique lo contrario, los identificadores como variables. Las excepciones a esta regla son los siguientes operadores:
Para definir una función anónima solo se ha de omitir el identificador.
bar = foo = ~() { << "FUNCIÓN ANÓNIMA"; };
foo(); // IMPRIME "FUNCIÓN ANÓNIMA";
bar(); // IMPRIME "FUNCIÓN ANÓNIMA";
La expresión parametrizada puede ser utilizada como dato para llevar a cabo operaciones sobre ella. Así es posible asignarla, llamarla con unos valores dado, ser devuelta como valores de función, etc.
Para definir una expresión parametrizada se utiliza el símbolo ``120#118'' seguido de una lista de parámetros separados por coma, el símbolo ``:'' y la expresión
a parametrizar, la cual normalmente hará uso de los parámetros. Es posible obtener el valor para unos determinados parámetros utilizando
el operador de llamada a función.
sum = ~ x, y : x + y; << sum (4,5);
Las funciones de orden superior son funciones que cumplen una de las siguientes premisas:
En OMI las funciones son tipos de datos, por lo que es posible crear funciones de orden superior de
forma natural a la sintaxis del lenguaje.
~ call (f) { // Funcón de orden superior que recibe una función como parámetro
f();
}
~ printA () { << "A" }
~ printB () { << "B" }
call (~&printA); // Imprime A
call (~&printB); // Imprime B
~ getOp (code) { // Función de orden superior que devuelve una función
switch (code) {
case 0: return ~(a,b) { return a + b }; break;
case 1: return ~(a,b) { return a - b }; break;
default: return ~(a,b) { << "WRONG CODE"; return null; }
}
}
func = getOp (0);
<< func (4, 5); // Imprime 9
func = getOp (1);
<< func (4, 5); // Imprime -1
func = getOp ("*");
<< func (4, 5); // Imprime "WRONG CODE"
La clausura permite que funciones que sean definidas en un entorno puedan acceder a las variables de dicho entorno.
~ addX (X) {
return ~(num) {
return num + X; // X pertenece al entorno de addX
}
}
add1 = addX(1);
add2 = addX(2);
<< add1 (4); // Imprime 5
<< add2 (4); // Imprime 6
~ factorial (num) {
if (num == 2) return 2;
return num * ~>(num-1);
}
En otros casos la función de contexto puede mantener otras referencias diferentes a la función en ejecución.
Un decorador es una función que:
Es posible crear un decorador mediante funciones gracias al principio de clausura.
~ generateFileHTML (func) { //Decorador mediante funciones
return ~(title, params){
<< "<html>";
<< "<head>";
<< "<title>" << title << "</title>";
<< "</head>";
<< "<body>";
func(params);
<< "</body>";
<< "</html>";
}
}
search = ~(params) { << "Buscando: " << params; };
info = ~(params) { << "Información sobre: " << params; };
generateSearchHTML = generateFileHTML (search);
generateInfoHTML = generateFileHTML (info);
generateSearchHTML("Búsqueda", "concepto");
generateInfoHTML("Información", "producto");
En OMI existe una construcción propia del lenguaje denominada decorador. Esta simplifica el
proceso anterior. Un decorador se define de forma parecida a una función: presenta un identificador
que lo nombra, una lista de parámetros y un bloque de sentencias. La lista de parámetros se corresponde
con los que recibirá la función devuelta por el decorador, la cual se definirá a partir del
bloque de sentencias. En el bloque de sentencias se podrá hacer referencia a la función a
decorar mediante el operador de función de contexto ``64#62''. Para definir un decorador se utiliza
el literal ``124#122''.
~~ generateFileHTML (title, params) { // Decorador mediante construcciones del lenguaje
<< "<html>";
<< "<head>";
<< "<title>" << title << "</title>";
<< "</head>";
<< "<body>";
~>(params);
<< "</body>";
<< "</html>";
}
search = ~(params) { << "Buscando: " << params; };
info = ~(params) { << "Información sobre: " << params; };
generateSearchHTML = generateFileHTML (search);
generateInfoHTML = generateFileHTML (info);
generateSearchHTML("Búsqueda", "concepto");
generateInfoHTML("Información", "producto");
La aplicación parcial toma una función y devuelve otra basada en esta, donde algunos de sus parámetros presentan ya un valor.
Para la aplicación parcial se utiliza el operador con el mismo nombre. Este consiste en el literal ``125#123'' seguidos de
una lista con los valores que se aplicarán y la función sobre la que se realizará la operación. La lista de parámetros consiste
en pares parámetro/valor separados por el signo ``86#84'', y entre si por coma. Los parámetros se han de corresponder con parámetros de
la función sobre la que se realizará la operación. La función resultante será una función que recibe tantos parámetros como la función original
exceptuando aquellos que tienen aplicados un valor.
~ addX (num, X) {
return num + X;
}
add4 = P[X=4]addX;
add8 = P[X=8]addX;
<< add4(5); // Imprime 9
<< add8(5); // Imprime 13
La definición de una lista por compresión debe ir entre paréntesis.
La forma más simple de crear una lista por compresión es mediante una expresión de generación, el literal 126#124 y una variable que iterará sobre una expresión conjunto. En cada iteración la variable iteradora tomará un valor del conjunto. Para cada elemento se calculará el valor de la expresión generadora, la cual normalmente contendrá la variable iteradora, y será introducido en el array resultado. El conjunto sobre el que se itera dependerá del tipo de dato obtenido al evaluar la expresión conjunto:
/*
Array de 10 elementos: desde el 0 al 9
*/
foo = ( y++ for x in y < 10 )
/*
Array de 10 elementos donde las
posiciones pares valen 0 y las
impares 1
*/
foo = ( x % 2 for x in 10 );
/*
Array que contiene "A", "B", "C" y "D"
*/
foo = ( x for x in "ABCD" );
/*
Array que contiene 1, 2 y 4
*/
foo = ( x/2 for x in {2,4,8} );
En cada iteración la expresión de conjunto es evaluada antes de obtener el elemento correspondiente.
Es posible filtrar los datos que serán incluidos en el array, añadiendo el literal
127#125 y la condición que se debe cumplir.
/*
Array de los pares
del 0 al 9
*/
foo = ( x for x in 10 if x % 2 == 0 );
Dado un array asociativo como conjunto es posible iterar sobre los pares clave/valor que lo conforman ,
para ello se define el par de variables que se van a utilizar separadas por ``128#126''.
/*
Array que contiene "key0 => val0"
y "key1 => val1"
*/
foo = ( k." => ".v for k:v in {"key0":"val0","key1":"val1"} );
El array generado puede ser asociativo utilizando dos expresiones de generación separadas por
``128#126'' que se utilizarán para construir las claves y el valores.
/*
Array asociativo con las claves "key0", "key1" y "key2",
cuyos valores son 2, 4 y 6 respectivamente.
*/
foo = ( "key".k : x for k:x in {2,4,6} );
Es posible indicar un bloque de sentencias que será ejecutado
con cada iteración. Este bloque irá después del conjunto a iterar.
/*
Array con los valores dados.
Se solicita valores y se van introduciendo
en el array hasta que no se da valor
*/
foo = ( x for i in x !== "" {>>["Posición ".i.": "] x} if x !== "" );
Una clase es una construcción del lenguaje que modela un concepto del dominio del problema. Se trata de la definición de un conjunto de entidades u objetos con características y propiedades comunes. Describe los datos necesarios para representarlos y el comportamiento que tienen asociado.
Los objetos son casos concretos de los conceptos modelados mediante las clases. Guardan un estado dando valores a los datos descritos. Además presentan el comportamiento ligado al concepto.
Los datos que representan el estado de un objeto son modelados mediante atributos, mientras que el comportamiento es definido mediante métodos.
En OMI una clase se construye mediante la palabra reservada ``129#127'', un identificador que la nombra y un bloque de sentencias en la que se
definirán los atributos y métodos. Un atributo se define de forma similar a una variable, y un método a una función.
/*
Se define una clase gato. Un gato
se representa mediante un nombre y tiene
asociado el comportamiento de aullar.
*/
class gato {
nombre;
~ aullar () {
<< "Miauuu";
}
}
Los objetos pueden ser creados mediante la instanciación de una clase. El objeto dará valores
a los atributos y se le podrá aplicar los métodos definidos. Al crear una instancia se
obtendrá un objeto llamando al método constructor, que es aquel que tenga el mismo nombre que la clase.
Para crear una instancia se utiliza el operador ``7#5'' seguido del nombre de la clase y los parámetros
del método constructor.
/*
Se define una clase gato. Un gato
se representa mediante un nombre y tiene
asociado el comportamiento de aullar.
*/
class gato {
nombre;
~ gato (nombre) {
<< "Construyendo el gato ".nombre;
}
~ aullar () {
<< "Miauuu";
}
}
/*
Se instancia un gato
llamado "MacAlistair"
*/
mac = new gato ("MacAlistair"); // Imprime "Construyendo el gato MacAlistair"
Si se instancia una clase que no se encuentra definida se produce un error semántico y se continua con la ejecución del programa.
Es posible acceder a los atributos y métodos de un objeto mediante el operador ``130#128''.
También se puede hacer referencia al objeto en ejecución dentro de los métodos, para ello se utiliza el operador ``58#56''.
/*
Se define una clase gato. Un gato
se representa mediante un nombre y tiene
asociado el comportamiento de aullar.
*/
class gato {
nombre;
~ gato (nombre) {
this->nombre = nombre;
}
~ aullar () {
<< "Miauuu";
}
}
/*
Se instancia un gato
llamado "MacAlistair"
*/
mac = new gato ("MacAlistair");
mac->aullar(); // Imprime "Miauuu"
<< mac->nombre; // Imprime "MacAlistair"
Si se accede a un atributo o método no existente se llaman al método ``_get'' o ``_call'' respectivamente (ver Métodos mágicos sec:magic_method). Si estos no existen se produce un error semántico y se continua con la ejecución.
Si no existe ningún método llamado igual que la clase, se considera que esta tiene un constructor vacío. Este es un método con el bloque de sentencias vacío y sin parámetros.
Una clase solo puede disponer de un constructor, si se dan varios métodos llamados igual que la clase solo tendrá efecto el último definido.
Los atributos y métodos públicos pueden ser accedidos desde un ámbito externo a la clase. A no ser que se especifique lo contrario todos los
métodos y atributos de una clase son públicos.
/*
Se define una clase gato. Un gato
se representa mediante un nombre y tiene
asociado el comportamiento de aullar.
*/
class gato {
nombre; // Atributo público
~ gato (nombre) { // Método público
this->nombre = nombre; // Acceso a atributo público dentro de la clase
}
~ aullar () { // Método público
<< "Miauuu";
}
}
/*
Se instancia un gato
llamado "MacAlistair"
*/
mac = new gato ("MacAlistair");
mac->aullar(); // Llamada a método público fuera de la clase
<< mac->nombre; // Acceso a atributo público fuera de la clase
Los métodos o atributos privados no son accesibles desde fuera de la propia clase, solo desde los métodos definido dentro de esta. Para
declarar un atributo o método como privado se antepone la palabra clave 131#129 a la definición.
/*
Se define una clase gato. Un gato
se representa mediante un nombre y tiene
asociado el comportamiento de aullar.
*/
class gato {
private nombre; // Atributo privado
~ gato (nombre) { // Método público
this->nombre = nombre; // Acceso a atributo privado dentro de la clase
}
~ aullar () { // Método público
<< "Miauuu";
}
}
/*
Se instancia un gato
llamado "MacAlistair"
*/
mac = new gato ("MacAlistair");
mac->aullar(); // Llamada a método público fuera de la clase
<< mac->nombre; // Acceso a atributo privado fuera de la clase (Error)
Si el constructor de una clase es privado solo se podrán crear instancias desde los métodos de esta, los cuales normalmente serán métodos estáticos.
En OMI es posible declarar un método o atributo como estático, esto hará que pertenezca a la clase y no a los objetos instanciados. Los atributos y métodos estáticos son accesibles utilizando la clase y no utilizando un objeto. Generalmente guardan datos o definen un comportamiento aplicables a un conjunto de objetos definido por la clase.
Para declarar un atributo o método como estático se utiliza la palabra reservada ``60#58''. Para acceder a un método o atributo se utiliza el
nombre de la clase, el operador de resolución de nombre de dominio ``132#130'' y el nombre del atributo o método. Es posible especificar que
un atributo o método será a la vez estático y privado.
class gato {
private nombre;
private raza;
private energia = 100;
private hambre = 100;
static private energia_min = -10;
static private hambre_min = -10;
static ~ pasar_tiempo (gatos, h = 1) {
if (h > 0) {
$(gatos) {
$->energia -= 10;
$->hambre -= (30 * h);
if ($->energia < gato::energia_min || $->hambre < gato::hambre_min)
<< $->nombre." a pasado a un lugar mejor";
}
}else{
<< "Imposible";
}
}
~ gato (nombre, raza) {
this->nombre = nombre;
this->raza = raza;
}
~ getNombre () {
return this->nombre;
}
~ getRaza () {
return this->raza;
}
~ aullar () {
<< "Miauuu";
}
~ comer () {
this->hambre += 10;
<< "El animal come";
}
~ dormir () {
this->energia += 10;
<< "El animal duerme";
}
~ jugar () {
if (this->energia <= 0 or this->hambre <= 0)
<< "El gato ".this->nombre." no quiere jugar.";
else{
<< "A ".this->nombre." le encanta jugar";
this->energia -= 10;
this->hambre -= 10;
}
}
}
mac = new gato ("MacAlistair", "Siamés");
ada = new gato ("Ada", "Persa");
bab = new gato ("Babbage", "Persa");
$(15) {
<< "Gatos disponibles";
<< "[0]=> ".mac->getNombre()." [".mac->getRaza()."] ";
<< "[1]=> ".ada->getNombre()." [".ada->getRaza()."] ";
<< "[2]=> ".bab->getNombre()." [".bab->getRaza()."] ";
<< "[otro]=> Salir ";
>>["Seleccione un gato para interactuar:"] s;
switch (s) {
case 0:
g = mac;
break;
case 1:
g = ada;
break;
case 2:
g = bab;
break;
default:
exit;
}
<< "------------------------------------------------------------";
<< "Acciones disponibles";
<< "[0]=> Dormir ";
<< "[1]=> Comer ";
<< "[2]=> Jugar ";
<< "[otro]=> Aullar ";
>>["Seleccione una acción:"] s;
switch (s) {
case 0:
g->dormir();
break;
case 1:
g->comer();
break;
case 2:
g->jugar();
break;
default:
g->aullar();
}
gato::pasar_tiempo ({mac, ada, bab});
}
En OMI es posible definir una relación de herencia entre dos clases. Que una clase herede de otra significa que es una especialización de esta.
La herencia permite establecer una jerarquía entre clases, tal que la clase que especializa es denominada hija de la clase que más genérica, denominada padre. La clase hija deriva de la padre y extiende su funcionalidad y definición. Una clase que extiende a otra toma todos sus métodos y atributos, pudiendo añadir nuevos o redefinir los existentes.
La herencia de clases permite que se dé la propiedad de polimorfismo, de forma que es posible mandar mensajes sintácticamente iguales a objetos de tipos distintos.
Para definir una relación de herencia entre dos clases se se sigue el nombre de la clase hija de la palabra
reservada ``133#131'' y del nombre de clase a la que extiende.
class mascota {
private nombre;
private raza;
~ mascota (nombre, raza) {
this->nombre = nombre;
this->raza = raza;
}
~ getNombre () {
return this->nombre;
}
~ getRaza () {
return this->raza;
}
}
class gato extends mascota {
~ aullar () {
<< "Miauuu";
}
}
class perro extends mascota {
~ aullar () {
<< "Guauuu";
}
}
mac = new gato ("MacAlistair", "Siamés");
ada = new perro ("Ada", "Bulldog");
mac->aullar(); // Imprime "Miauuu"
ada->aullar(); // Imprime "Guauuu"
Si alguna clase hija no define constructor se toma el constructor de la clase padre si esta tuviera.
Es posible definir una serie de clases de forma que presenten varios niveles de jerarquía.
Desde un método definido en la clase hija es posible hacer referencia a los métodos definidos en la clase padre
mediante la palabra reservada ``59#57''.
class mascota {
private nombre;
private raza;
~ mascota (nombre, raza) {
this->nombre = nombre;
this->raza = raza;
}
~ getNombre () {
return this->nombre;
}
~ getRaza () {
return this->raza;
}
}
class gato extends mascota {
~ aullar () {
<< "Miauuu";
}
}
class gato_persa extends gato {
~ gato_persa (nombre) {
parent->gato (nombre, "Persa"); // Llama al método constructor de la clase padre.
}
}
mac = new gato_persa ("MacAlistair");
mac->aullar(); // Imprime "Miauuu"
<< mac->getRaza(); // Imprime "Persa"
Cuando un método de una clase padre utiliza el operador ``58#56'' se accede al objeto
en si, de la misma forma que se haría desde un método de la clase hija.
class mascota {
private nombre;
private raza;
~ mascota (nombre, raza) {
this->nombre = nombre;
this->raza = raza;
}
~ getNombre () {
return this->nombre;
}
~ getRaza () {
return this->raza;
}
~ saludar () {
// Accede al método getType definido en la clase hija
<< "Hola soy ".this->nombre.", un ".this->getType()." ".this->raza;
}
}
class gato extends mascota {
~ getType () {
return "gato";
}
~ aullar () {
<< "Miauuu";
}
}
class perro extends mascota {
~ getType () {
return "perro";
}
~ aullar () {
<< "Guauuu";
}
}
mac = new gato ("MacAlistair", "Persa");
mac->aullar(); // Imprime "Miauuu"
mac->saludar(); // Imprime "Hola soy MacAlistair, un gato Persa"
ada = new perro ("Ada", "Bulldog");
ada->aullar(); // Imprime "Guauuu"
ada->saludar(); // Imprime "Hola soy Ada, un perro Bulldog"
En OMI es posible acceder a un atributo o método estático que es definido en la clase hija, desde un método de
la clase padre. Para ello se utiliza la palabra reservada ``60#58'' seguido del operador ``132#130'' y el
nombre del atributo o método al que se accederá.
class mascota {
private nombre;
private raza;
~ mascota (nombre, raza) {
this->nombre = nombre;
this->raza = raza;
}
~ getNombre () {
return this->nombre;
}
~ getRaza () {
return this->raza;
}
~ saludar () {
// Accede al atributo estático type definido en la clase hija
<< "Hola soy ".this->nombre.", un ".static::type." ".this->raza;
}
}
class gato extends mascota {
static type = "gato";
~ aullar () {
<< "Miauuu";
}
}
class perro extends mascota {
static type = "perro";
~ aullar () {
<< "Guauuu";
}
}
mac = new gato ("MacAlistair", "Persa");
mac->aullar(); // Imprime "Miauuu"
mac->saludar(); // Imprime "Hola soy MacAlistair, un gato Persa"
ada = new perro ("Ada", "Bulldog");
ada->aullar(); // Imprime "Guauuu"
ada->saludar(); // Imprime "Hola soy Ada, un perro Bulldog"
En OMI no es posible llevar a cabo herencia múltiple. Tampoco dispone de ningún mecanismo con el que se pueda simular tales como traits.
Para redefinir una clase basta con definirla nuevamente con el mismo identificador. La nueva
clase tomará todos los atributos y métodos anteriores.
class miClase {
~ meth1 () {
<< "Método 1";
}
}
class miClase {
~ meth2 () {
<< "Método 2";
}
}
foo = new miClase ();
foo->meth1(); // Imprime "método 1"
foo->meth2(); // Imprime "método 2"
class miClase {
~ meth1 () {
<< "Redefinición de método 1";
}
}
foo = new miClase ();
foo->meth1(); // Imprime "Redefinición de método 1"
foo->meth2(); // Imprime "Método 2"
La redefinición de clases es un mecanismo que ofrece flexibilidad a la hora de construir clases de objetos, no obstante debe ser utilizado con cuidado ya que hace que la definición de una clase sea dinámica y pueda cambiar en la ejecución pudiendo ocasionar confusión en el código e inestabilidad del programa.
Es posible redefinir una clase base y modificar su comportamiento, añadiendo o sustituyendo
métodos.
class stringClass {
~ OReq (elems) {
$(elems)
if (this == $)
return true;
return false;
}
~ concatX () {
return this .= "X";
}
}
foo = "ABC"->OReq({"A", "AB", "ABC"}); // foo vale true
foo = "ABCD"->OReq({"A", "AB", "ABC"}); // foo vale false
foo = "ABCD"->concatX(); // foo vale "ABCDX"
str = "ABCD";
<< str->concatX(); // Imprime "ABCDX"
<< str; // Imprime "ABCDX"
En una clase base el operador ``58#56'' obtiene el dato en si, por lo que este será tratado como una variable de este tipo.
Es posible extender una clase base para especializarla mediante otras subclases.
class stringA extends stringClass {
~ stringA (){
this = "A";
}
}
class stringB extends stringClass {
~ stringB (){
this = "B";
}
}
strA = new stingA ();
strB = new stingB ();
<< strA; // Imprime "A"
<< strB; // Imprime "B"
<< strA . strB; // Imprime "AB"
Las clases bases logicClass y arithClass no disponen inicialmente de métodos. Las clases stringClass y arrayClass tienen como métodos los equivalentes a las funciones aplicables a estos tipos de datos.
OMI es un lenguaje de tipado dinámico que presenta un estilo duck typing. La validez semántica en el uso de un objeto viene determinada por el conjunto de atributos y métodos de este.
Si en una expresión se accede a un método o atributo de un objeto únicamente se
comprobará que el elemento exista, indistintamente de la clase a la que pertenezca
el objeto. De esta forma es posible utilizar un objeto independientemente de
la clase a la que pertenece, sin que sea necesario un chequeo de tipos.
class pato {
~ sonar () {
<< "Cuack";
}
}
class persona {
~ sonar () {
<< "La persona imita el sonido de un pato";
}
}
~ sonar (obj) {
/*
* Sea cual sea la clase del objeto,
* si tiene el método sonar
* será llamado
*/
obj->sonar();
}
obj1 = new pato ();
obj2 = new persona ();
sonar (obj1); // Imprime "Cuack"
sonar (obj2); // Imprime "La persona imita el sonido de un pato"
El estilo de tipado duck typing permite que se de polimorfismo sin herencia de clases, de forma que a objetos de distinto tipos se le puede enviar mensajes sintácticamente iguales.
En OMI se definen una serie de métodos mágicos. Estos son métodos que serán llamados en distintas circunstancias, como cuando se utilice al objeto como una cadena o se acceda a un atributo no existente.
Los métodos mágicos comienzan por el signo ``_'' y son los siguientes:
El método ``_str'' no tendrá parámetros y devolverá la cadena de caracteres que represente el objeto.
class persona {
private nombre;
private apellidos;
~ persona (nombre, apellidos) {
this->nombre = nombre;
this->apellidos = apellidos;
}
~ _str () {
return this->apellidos.', '.this->nombre;
}
}
p = new persona ('Fco. Javier', 'Bohórquez Ogalla');
<< p; // Imprime "Bohórquez Ogalla, Fco. Javier"
El método ``_get'' tendrá un único parámetro que se corresponderá con
el nombre del atributo no existente al que se ha accedido. Y devolverá
el valor para dicho atributo.
class persona {
private nombre;
private apellidos;
~ persona (nombre, apellidos) {
this->nombre = nombre;
this->apellidos = apellidos;
}
~ _get (attr) {
<< "El atributo ".attr." no existe";
return "No existe";
}
}
p = new persona ('Fco. Javier', 'Bohórquez Ogalla');
foo = p->test; // Imprime "El atributo test no existe"
<< foo; // Imprime "No existe"
El método ``_call'' tendrá dos parámetros que se corresponderán con
el nombre del método no existente al que se ha accedido y
con un array que contendrá los parámetros pasados en la llama. El valor devuelto
será el valor de la llamada.
class persona {
private nombre;
private apellidos;
~ persona (nombre, apellidos) {
this->nombre = nombre;
this->apellidos = apellidos;
}
~ _call (meth, params) {
<."El método ".meth." no existe [ ";
$(params) <.$." ";
<< "]";
return "No existe";
}
}
p = new persona ('Fco. Javier', 'Bohórquez Ogalla');
foo = p->test("1", 2, "4"); // Imprime "El método test no existe [ 1 2 4 ]"
<< foo; // Imprime "No existe"
En OMI es posible acceder al entorno en el que se ejecuta el programa, definido por el sistema operativo y la entrada del programa.
La mayoría de sistemas operativos permiten definir una serie de variables de entorno que pueden ser usadas
por los procesos y por el propio sistema operativo. En OMI es posible acceder a estas variables
mediante la función 134#132 y una cadena que representa el nombre de la variable a la que se desea acceder.
<< getenv ("USER"); // Imprime el nombre de usuario
Un programa escrito en OMI puede recibir una serie de parámetros cuando es ejecutado. Los argumentos serán almacenados
en una variable denominada 135#133. El valor de esta será un array donde el primer elemento es el nombre del script y los siguientes
los argumentos dados.
// File: args.omi $(args) << $; // Imprime el nombre del script y los argumentos.
{language=bash}
omi args.omi "ARG1" "ARG2" # Imprime args.omi ARG1 y ARG2
Las excepciones son un mecanismo de programación que permite tratar casos no habituales, normalmente de error, durante la ejecución de un programa. Las excepciones permiten separar el código correspondiente al caso de normal o de éxito, del código extraordinario o de error.
El uso de excepciones permite realizar programas robustos y tolerantes a errores, a la vez que se gana claridad en el código.
Para hacer uso del manejo de excepciones se utiliza la sentencia
107#105, esta consiste en la palabra reservada 136#134 seguida del bloque de
sentencias en la que puede darse la excepción. Tras lo cual se utiliza la palabra reservada 108#106, un símbolo variable al que se le va a asignar el valor de la excepción y un bloque de
sentencias en la que se va a tratar. Para lanzar la excepción se utilizar la palabra reservada 110#108 seguida de una expresión que le da valor.
class Exc {
var = null;
~ Exc (num) {
this->var = num;
}
~ printError () {
<< "Error " << this->var;
}
}
try {
>>["Dame un valor:"] foo;
if ( foo === "" ){
throw new Exc (404);
}
<< foo;
}catch (exc) {
exc->printError ();
}
A diferencias de otros lenguajes de programación OMI solo permite un bloque catch que será ejecutado sea cual sea la excepción que se produzca. No se comprobará el tipo de dato del valor asociado a la excepción.
En OMI es posible utilizar expresiones cuyo valor sea una cadena de caracteres como un identificador para una variable, una función, una clase, un método, etc. Para ello se
utiliza el símbolo ``137#135'' seguido de la expresión entre llaves.
class Foo {
~ hello () {
<< "Hola mundo";
}
}
class_pre = "Fo";
class_post = "o";
var = "object";
method = "hello";
@{var} = new @{class_pre.class_post}();
object->@{method}(); // Imprime "Hola mundo"
En OMI también es posible evaluar una cadena de caracteres como si de una sentencia de código fuente se tratase. Para ello se utiliza la sentencia ``138#136''.
foo = " x = 4 + 3 "; eval (foo); << x; // Imprime 7
OMI es un lenguaje en el que es posible llevar a cabo introspección de tipos. Permite examinar, en tiempo de ejecución, el tipo de los objetos creados.
Para obtener la clase de un objeto dado se utiliza la palabra clave ``139#137'' seguida de la expresión, cuyo valor representará el objeto, entre paréntesis. Se devolverá
la cadena de caracteres correspondiente al nombre de la clase, o la cadena vacía si la expresión no es un objeto.
class Foo {
~ hello () {
<< "Hola mundo";
}
}
foo = new Foo ();
if (getclass(foo) == 'Foo')
foo->hello(); // Imprime "Hola mundo"
|æ| = 10; Error: lexical error, unexpected character (|æ|)
[ id ); Error: syntax error, unexpected |')'|, expecting |','| or |']'|
Cuando una sentencia o expresión es interpretada es posible que se dé un error semántico si no es capaz de operar sobre los datos facilitados, o no se da el contexto adecuado.
Existe gran variedad de posibles errores semánticos, estos son descritos en este manual al tratar las sentencias, expresiones y demás construcciones en las que se
pueden dar. Cuando se produce un error semántico puede detenerse o no la ejecución del programa, dependerá del error en si. Si no se indica lo contrario en
el manual, cuando se produce un error semántico, se prosigue con la ejecución del programa.
a = new noExist () Error: data type error, worng |class| identifier
OMI es un lenguaje de tipado dinámico, por lo que cualquier error de tipo se da en tiempo de ejecución. Las funciones y operadores del lenguaje esperan tratar con unos tipos de datos concretos. Si una función u operador recibe un valor de un tipo que no pueda manipular producirá un error semántico. En este manual se indica los tipos de datos con los que esperan operar las funciones y métodos, se entiende que otro tipo distinto producirá un error.
En OMI las cadenas de caracteres pertenecen a una clase de objetos, así toda función aplicable a una cadena puede ser llamada como un método de la cadena.
Toda función aplicable a una cadena comienza con el prefijo ``str'' y toman como primer parámetro la cadena sobre la que operar. Por otro lado los métodos de cadena no disponen de prefijo y se toma implícitamente como primer parámetro el objeto cadena.
140#138
// foo es el array {"A", "B", "C", "D", "E"}
foo = str_explode ("A,B,C,D,E", ",");
// foo es el array {"A", "B", "C", "D", "E"}
foo = "A,B,C,D,E"->explode (",");
141#139
foo = str_find ("A,B,C,D,C,F", "C"); // foo vale 4
foo = "A,B,C,D,C,F"->find ("C"); // foo vale 4
foo = str_find ("A,B,C,D,C,F", "C", 5); // foo vale 8
foo = "A,B,C,D,C,F"->find ("C", 5); // foo vale 8
foo = str_find ("A,B,C,D,C,F", `C(.*)C`); // foo vale 4
foo = "A,B,C,D,C,F"->find (`C(.*)C`); // foo vale 4
142#140
// foo vale "A,B,P,D,P,F"
foo = str_replace ("A,B,C,D,C,F", "C", "P");
// foo vale "A,B,P,D,P,F"
foo = "A,B,C,D,C,F"->replace ("C", "P");
// foo vale "A,B,P,D,C,F"
foo = str_replace ("A,B,C,D,C,F", "C", "P", 1);
// foo vale "A,B,P,D,C,F"
foo = "A,B,C,D,C,F"->replace ("C", "P", 1);
// foo vale "A,B,D,F"
foo = str_replace ("A,B,C,D,C,F", `C,(.)`, "\1");
// foo vale "A,B,D,F"
foo = "A,B,C,D,C,F"->replace (`C,(.)`, "\1");
143#141
// foo vale "A,B,P,C,F"
foo = str_replace_sub ("A,B,C,D,C,F", 4, 3, "P");
// foo vale "A,B,P,C,F"
foo = "A,B,C,D,C,F"->replace_sub (4, 3, "P");
144#142
foo = str_upper ("a,b,c,D"); // foo vale "A,B,C,D"
foo = "a,b,c,D"->upper (); // foo vale "A,B,C,D"
145#143
foo = str_lower ("A,B,C,d"); // foo vale "a,b,c,d"
foo = "A,B,C,d"->lower (); // foo vale "a,b,c,d"
146#144
atag = `(?i)<a[^>]*href\s*=\s*\"([^\"]*)\"[^>]*>(.+?)<\s*/\s*a\s*>`;
web = "<a href=\"url01\">link01 -> web01< /a>";
web .= "\n<div><a id=\"id\" href=\"url02\" class=\"clase\">link02 -> web02</a></div>";
/*
links es el siguiente array:
{
{
"url" : "url01",
"label" : "link01 -> web01"
},
{
"url" : "url02",
"label" : "link02 -> web02"
}
}
*/
links = str_search (web, atag, "url", "label");
Las cadenas de caracteres tienen el método search, que opera de igual forma que la función str_search, salvo por que no acepta la lista de claves.
147#145
foo = str_match ("ABCD",`A[^D]*D`); // foo vale true
foo = "ABCD"->match(`A[^D]*D`); // foo vale true
148#146
pattern = regexp ("A[^D]*D");
foo = "ABCD"->match(pattern); // foo vale true
149#147
<< sprintf("Esto es una %s formateada.", "cadena"); // Esto es una cadena formateada.
<< sprintf("Ya son %2d", 2.003); // Ya son 02
<< sprintf("Y %4s", "tres gatos"); // Y tres
<< sprintf("Y %+4i", "4"); // Y +0004
<< sprintf("Y %u", -5); // Y 5
<< sprintf("Y %+2f", 6.6666 ); // Y +6.7
<< sprintf("Y %+5e", 777.77777777); // Y +7.77778e+02
<< sprintf("Y %F", "88888888888888888888888888"); // Y 8.88889E+25
<< sprintf("Y %3E", "99.99"); // Y 9.999E+01
<< sprintf("Y %^s", "diez"); // Y DIEZ
<< sprintf("%d en octal es %#o", 31, 31); // 31 en octal es 037
// 31 en hexadecimal es 0x1f o 0X1F
<< sprintf("%d en hexadecimal es %#x o %#X", 31, 31, 31);
<< sprintf("Texto sin expresiones de formato"); // Texto sin expresiones de formato
<< sprintf("%2sdenas %3stas", "Cadiz", "junio"); // Cadenas juntas
// El 100% de aciertos en 100 intento
<< sprintf("El %s%% de aciertos en %d intentos", 100, 100);
<< sprintf("%%%%%%"); // %%%%
En OMI los arrays pertenecen a una clase de objetos, así toda función aplicable a un array puede ser llamada como un método del mismo.
Toda función aplicable a un array con el prefijo ``array'' y toman como primer parámetro el array sobre el que operar. Por otro lado los métodos de array no disponen de prefijo y se toma implícitamente como primer parámetro el objeto array.
150#148
<< array_implode ({"Hola", "Mundo"}, " "); // Imprime "Hola Mundo"
<< {"Hola", "Mundo"}->implode (" "); // Imprime "Hola Mundo"
151#149
<< array_first ({"Hola", "Mundo"}); // Imprime "Hola"
<< {"Hola", "Mundo"}->first (); // Imprime "Hola"
152#150
<< array_last ({"Hola", "Mundo"}); // Imprime "Mundo"
<< {"Hola", "Mundo"}->last (); // Imprime "Mundo"
153#151
/*
* foo vale el array { "Hola", "Mundo", "Digital"}
*/
foo = array_insert ({"Hola", "Mundo"}, 2, "Digital");
/*
* foo vale el array { "Hola", "Mundo", "Digital"}
*/
foo = {"Hola", "Mundo"}->insert (2, "Digital");
foo = {"A", "C"};
array_insert (foo, 1, "B"); // foo vale {"A", "B", "C"}
foo->insert (3, "D"); // foo vale {"A", "B", "C", "D"}
154#152
/*
* foo vale el array { "Hola" }
*/
foo = array_delete ({"Hola", "Mundo"}, 1);
/*
* foo vale el array { "Hola" }
*/
foo = {"Hola", "Mundo"}->delete (1);
foo = {"A", "C"};
array_delete (foo, 0); // foo vale {"C"}
foo->delete (0); // foo vale {}
foo = { 'key0' : 'val0', 'key1' : 'val1' };
foo->delete ('key0'); // foo vale { 'key1' : 'val1' }
155#153
/*
* foo vale el array { "Hola", "Mundo", "Digital" }
*/
foo = array_push ({"Hola", "Mundo"}, "Digital");
/*
* foo vale el array { "Hola", "Mundo", "Digital"}
*/
foo = {"Hola", "Mundo"}->push ("Digital");
foo = {"A", "B"};
array_push (foo, "C"); // foo vale { "A", "B", "C" }
foo->push ("D"); // foo vale { "A", "B", "C", "D" }
foo = { 'key0' : 'val0', 'key1' : 'val1'};
foo->push ('val2'); // foo vale { 'key0' : 'val0', 'key1' : 'val1', '2' : 'val2' }
156#154
/*
* foo vale la cadena "Mundo"
*/
foo = array_pop ({"Hola", "Mundo"});
/*
* foo vale la cadena "Mundo"
*/
foo = {"Hola", "Mundo"}->pop ();
foo = {"A", "B"};
array_pop (foo); // foo vale { "A" }
foo->pop (); // foo vale { }
foo = { 'key0' : 'val0', 'key1' : 'val1'};
foo->pop (); // foo vale { 'key0' : 'val0' }
157#155
/*
* foo vale el array { "Digital", "Hola", "Mundo" }
*/
foo = array_unshift ({ "Hola", "Mundo" }, "Digital");
/*
* foo vale el array { "Digital", "Hola", "Mundo"}
*/
foo = {"Hola", "Mundo"}->unshift ("Digital");
foo = {"B", "C"};
array_unshift (foo, "A"); // foo vale { "A", "B", "C" }
foo->unshift ("-"); // foo vale { "-", "A", "B", "C" }
foo = { 'key0' : 'val0', 'key1' : 'val1'};
foo->unshift ('val2'); // foo vale { 'key0' : 'val2', 'key1' : 'val0', '2' : 'val1' }
158#156
/*
* foo vale la cadena "Hola"
*/
foo = array_shift ({"Hola", "Mundo"});
/*
* foo vale la cadena "Hola"
*/
foo = {"Hola", "Mundo"}->shift ();
foo = {"A", "B"};
array_shift (foo); // foo vale { "B" }
foo->shift (); // foo vale { }
foo = { 'key0' : 'val0', 'key1' : 'val1'};
foo->shift (); // foo vale { 'key1' : 'val1' }
159#157
/*
* foo vale la cadena "Hola Mundo Digital"
*/
foo = array_reduce (
{"Hola", "Mundo", "Digital"},
~(acumulador, elemento) {
return acumulador." ".elemento;
}
);
/*
* foo vale la cadena "Hola Mundo Digital"
*/
foo = {"Hola", "Mundo", "Digital"}->reduce (
~(acumulador, elemento) {
return acumulador." ".elemento;
}
);
Es posible enviar señales a los procesos creados, y estos podrán manejarlas según sea necesario.
160#158
/*
* foo vale el listado de procesos en ejecución
*/
foo = exec ('ps -e');
161#159
/*
* Se lleva a cabo la bifurcación de la ejecución mediante la
* creación de un proceso que imprimirá "Soy el proceso Hijo",
* mientras que en el proceso principal imprimirá
* "Soy el proceso Padre de PID" donde PID es el identificador
* del proceso hijo.
*/
if (pid = fork ())
<< "Soy el proceso Padre de " . pid;
else
<< "Soy el proceso Hijo";
162#160
/*
* Se crea un proceso y se espera
* a que este finalice
*/
if (pid = fork ()) {
wait( pid );
<< "Finaliza proceso padre";
}else{
sleep (10);
<< "Finaliza proceso hijo";
}
163#161
/*
* Se crea un proceso y se imprimen
* los identificadores.
*/
if (pid = fork ()) {
<< "PID del Padre " . getpid (); // Imprime el pid del padre
<< "PID del Hijo " . pid; // Imprime el pid del hijo
} else {
<< "PID del Hijo " . getpid (); // Imprime el pid del hijo
}
164#162
/*
* Se crea un proceso y se imprimen
* los identificadores.
*/
if (pid = fork ()) {
<< "PID del Padre " . getpid (); // Imprime el pid del padre
<< "PID del Hijo " . pid; // Imprime el pid del hijo
} else {
<< "PID del Padre " . getppid (); // Imprime el pid del padre
<< "PID del Hijo " . getpid (); // Imprime el pid del hijo
}
165#163
/*
* Se crea un proceso y envía una señal pasados
* unos segundos para finalizar su ejecución
*/
if (pid = fork ()) {
<< "Padre: Esperando para finalizar";
sleep (2);
<< "Padre: Enviando señal";
signal (pid, 9);
} else {
<< "Hijo: Esperando señal";
sleep (30);
<< "Hijo: Finaliza normal";
}
166#164
/*
* Se crea un proceso y envía una señal
* que será manejada
*/
if (pid = fork ()) {
<< "Padre: Esperando para interrumpir";
sleep (2);
<< "Padre: Enviando interrupción";
signal (pid, 2);
} else {
if (shandler (2, ~(){ << "Interrupción"; })){
sleep (30);
<< "Hijo: Finaliza normal";
}else {
<< "Error estableciendo manejador de señal";
}
}
167#165
/*
* Se crea un proceso y si el identificador de
* proceso es menor que 20000 se cierra.
*/
if (pid = fork ()) {
sleep (30);
} else {
if (getpid() < 20000){
exit_process ();
}
<< "Exit";
}
168#166
process ( ~() { $(100) << "P1:".$; } );
process ( ~(i) { $(i) << "P2:".$; }, 100 );
OMI presenta un tipo de dato especial que se corresponde con un puntero a fichero. Este tipo de dato sólo puede ser usado en las funciones de fichero, en cualquier otro contexto tomará un valor booleano que indica si el fichero se encuentra abierto.
Un puntero a fichero referencia una posición dentro del mismo, posición a partir de la cual se llevarán a cabo las operaciones de lectura/escritura. Esta posición puede ser manipulada.
OMI solo es capaz de operar directamente con ficheros en texto plano. No es posible crear ficheros de tipo binario que no sea texto plano.
169#167
if (f = file ("example.file", "w+")){
<< "Fichero abierto con é|xito";
} else {
<< "Error abriendo el fichero";
}
170#168
if (f = file ("example.file", "w+")){
fclose (f);
} else {
<< "Error abriendo el fichero";
}
if (f = file ("example.file", "w+")){
f>: (f, "Contenido ha escribir");
fclose(f);
} else {
<< "Error abriendo el fichero";
}
172#170
if (f = file ("example.file", "w")){
f>: (f, "Contenido escrito");
fclose(f);
} else {
<< "Error abriendo el fichero";
}
if (f = file ("example.file", "r")){
<< f<:(f); // Imprime "Contenido escrito"
fclose (f);
} else {
<< "Error abriendo el fichero";
}
173#171
if (f = file ("example.file", "w+")){
f>:(f, "ABCD");
fseek (f, 2);
<< f<:(f, 1); // Imprime "C"
fseek (f, -1, FEND);
<< f<:(f, 1); // Imprime "D"
fseek (f, -1, FCUR);
<< f<:(f, 1); // Imprime "C"
fseek (f, -1, FCUR);
<< f<:(f, 1); // Imprime "B"
fseek (f, -1, FCUR);
<< f<:(f, 1); // Imprime "A"
fclose(f);
} else {
<< "Error abriendo el fichero";
}
174#172
if (f = file ("example.file", "w+")){
f>:(f, "ABCD");
fseek (f, 2);
<< "Antes de leer: " . ftell (f); // Imprime 2
<< f<:(f, 1); // Imprime "C"
<< "Después de leer: " . ftell (f); // Imprime 3
fclose(f);
} else {
<< "Error abriendo el fichero";
}
175#173
foo = fread ('/etc/hosts'); // foo vale el contenido del fichero /etc/hosts
176#174
/*
* El fichero /etc/hosts queda
* con el contenido:
* 127.0.0.1 localhost
*/
fwrite ('/etc/hosts', '127.0.0.1 localhost');
177#175
/*
* Al fichero /etc/hosts se le
* ha añadido el contenido:
* 127.0.0.1 localhost
*/
fappend ('/etc/hosts', '127.0.0.1 localhost');
178#176
<< date ("%d/%m/%Y %H:%i:%s"); // Fecha y hora actual
<< date ("%d/%m/%Y %H:%i:%s", 0); // 01/01/1970 00:00:00
179#177
<< time (); // Imprime la marca de tiempo Unix actual
<< date ("%d/%m/%Y %H:%i:%s", time()); // Fecha y hora actual
Esta es una función interna de OMI que puede ser utilizada sin paréntesis.
Los módulos son bibliotecas dinámicas que son cargadas en tiempo de ejecución. Estas han sido programadas en un lenguaje de más bajo nivel (C/C++) y compiladas a código máquina.
Es posible obtener módulos de OMI desde la web del proyecto (http://wwww.omi-project.com/download/extensions).
load ('libomi_gettext.so');
<< _('cadena traducida');
Se dispone de un mecanismo para autocargar módulos de extensiones del lenguaje. Al iniciarse el intérprete se cargarán todos los módulos indicados en el fichero ``libs.ini'' localizado en el directorio de datos de la aplicación (por defecto /usr/local/share/omi). Cada línea de este fichero será un módulo que será cargado, exceptuando las líneas que comiencen por ``;'' que son consideradas comentarios.
Para construir un módulo se ha de disponer de la biblioteca OMI correctamente instalada. Para ello se dispone de dos opciones:
Un módulo OMI está escrito en C++ y se define mediante unas series de clases cada una de las cuales representará una función que se desea añadir.
El intérprete OMI se construye mediante una colección de nodos ejecutables. Todo es un nodo ejecutable: los booleanos, los números, las variables, las funciones... Existen diferentes niveles de abstracción en los nodos ejecutables, así por ejemplo un número es representado mediante un nodo ejecutable numérico, que a su vez es un nodo expresión aritmética, que a su vez es una expresión de tipo definido... Las clases de un módulo extenderán los tipos de nodos definidos por la biblioteca de desarrollo OMI, normalmente nodos expresiones.
Las clases que integran un modulo OMI deben cumplir lo siguiente:
Dependiendo el tipo de nodo ejecutable que se tome de base se dispondrá de un atributo que guardará el valor interno del nodo. Así un nodo de tipo cadena de caracteres tendrá un atributo que representa la cadena guardada. El método 182#180 llevará a cabo la funcionalidad y dará valor a este atributo.
Normalmente el método 182#180 obtendrá los parámetros con los que se ha llamado a la
función, les aplicará la operativa asociada y atribuirá el valor interno del
nodo. Mediante una llamada al método 183#181 es posible obtener un vector
con los parámetros pasados a la función en una ejecución.
language=cpp
// libomi_strings.h
#include <omi/omi.h>
class ucfirstNode : public stringNode, public extensionNode {
public:
using extensionNode::extensionNode; // Si std=c++11
// ucfirstNode (listNode* list) : extensionNode(list) {} //Si std=c++98
void run ();
};
// libomi_strings.cpp
#include "libomi_strings.h"
void ucfirstNode::run () {
vector<runNode*> v = this->getParams ();
if (v.size() == 1){
strvalue_ = stringNode::to_str (v[0]);
strvalue_[0] = toupper(strvalue_[0]);
}else
throw errorException(
"One parameter is required",
"ucfirstNode::run, size " + to_string (v.size()),
ERROR_PARAMS
);
}
extern "C" void load (PluginsLoader* loader) {
loader->add ("ucfirst", create<ucfirstNode>);
}
language=bash
$PS1 # Compilación del módulo $PS1 g++ -c -fPIC \ $PS2 -DNUMTYPE='double' -DNUMPRECISION='15' -DREFCTYPE='unsigned int' \ $PS2 -ansi -pedantic -g -std=c++11 libomi_strings.cpp -o libomi_strings.o $PS1 g++ -g -O2 -shared -rdynamic -g -o libomi_strings.so libomi_strings.o -lomi
language=omi
// user.omi
load ("./libomi_strings.so")
<< ucfirst ("example"); // Imprime "Example"
En este caso el intérprete leerá por el puerto establecido una cadena de caracteres que representa el código a interpretar. El intérprete procesará la cadena y producirá un resultado.
Cuando se ejecuta el intérprete en de esta forma en conjunción con una arquitectura cliente/servidor esta estructura es devuelta por el puerto establecido.
La interfaz de runTree se divide como sigue:
This document was generated using the LaTeX2HTML translator Version 2008 (1.71)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -no_subdir -split 0 head.tex -html_version 4.0,latin1,unicode
The translation was initiated by franj on 2016-01-31
franj 2016-01-31