Image logo-doc

Manual de usuario
Fco. Javier Bohórquez Ogalla



Índice General

Introducción

OMI, acrónimo de Open Modular Interpreter, es un lenguaje para la creación de `scripts' que presenta un propósito general y es de código abierto. El objetivo del proyecto OMI es servir de guía en la aplicación práctica de la teoría de compiladores e intérpretes. Para ello presenta un caso práctico en el que se construye un lenguaje de programación moderno a partir de los conceptos teóricos de autómatas y lenguajes formales.

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.

Características

3#1

Requisitos previos

El intérprete OMI puede ser ejecutado sobre cualquier hardware actual. El software por si mismo solo necesita unos pocos kilobytes de memoria para funcionar, sin embargo el hardware necesario dependerá en gran medida del código fuente que será interpretado.

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.

Uso del sistema

Obtener OMI

El intérprete OMI puede ser descargado desde la web OMI project. Se ofrecen varias alternativas para la descarga:

Se recomienda la descarga de la última versión estable del sistema software.

Instalación

El intérprete OMI puede ser instalado sobre un sistema GNU/Linux mediante varios procesos.


Compilación e instalación desde código fuente

Para instalar OMI mediante el código fuente de la aplicación previamente se deberá llevar a cabo la compilación de este. Es posible obtener el código fuente de la aplicación desde la sección de descargas de la web OMI project.

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:

numType:
Tipo de dato utilizado para la representación interna de los datos numéricos. El valor por defecto es ``double''. Su valor puede ser cualquier tipo de dato estándar de C/C++ o algún tipo específico facilitado por el usuario. También admite el valor en número de bytes, siendo los posibles valores ``4bytes'', ``8bytes'' y ``16bytes''.
numPrecision:
Precisión numérica utilizada en al escribir en la salida estándar. Su valor por defecto es 15, y puede ser cualquier número entero.
refCType:
Tipo de dato utilizado para contar el número de referencias de los nodos. Determina el número de referencias máximas que puede llegar a tener un dato. Su valor por defecto ``unsigned int''. Puede ser cualquier tipo de dato estándar de C/C++ o algún tipo específico facilitado por el usuario.
sizeNode:
Esta opción permite configurar el intérprete dando valores óptimos a las opciones anteriores. Puede presentar uno de los siguientes valores:
small:
numType=``float'' refCType=``unsigned int'' numPrecision=6
normal:
numType=``double'' refCType=``unsigned int'' numPrecision=15
big:
numType=``long double'' refCType=``unsigned int'' numPrecision=18

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


Instalación mediante paquete .deb

Cada versión del intérprete es precompilada y empaquetada. Las bibliotecas externas se compilan de forma estática por lo que se elimina la dependencia con otros paquetes.

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

Intérprete

labelsec:intérpreter El intérprete es el sistema software sobre el que gira el proyecto OMI. Se trata del software encargado de leer y ejecutar el código fuente escrito en 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:

4#2

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:

-c cmd:
Interpreta la cadena cmd.
-i:
Modo interactivo. Las expresiones y sentencias son interpretadas en tiempo real y solicitadas mediante un prompt.
-h:
Muestra la ayuda del comando.
-V:
Muestra la versión del software.

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:

5#3

Además los recursos, como por ejemplo las extensiones, serán ubicados en el siguiente directorio:

6#4

language=omi

Referencias del lenguaje


Sentencias

Como muchos otros lenguajes de programación toda sentencia OMI debe acabar en punto y coma ``;''.

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"


Comentarios

Los comentarios son fragmentos de texto que son incluidos en el código fuente y que serán omitidos por el intérprete. Se utilizan normalmente para aclarar y/o documentar las piezas de código.

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;


Ejecución

La ejecución de un programa escrito en OMI comenzará desde un fichero, y desde la primera sentencia contenida en este. Las sentencias en este fichero que no esten contenidas en ninguna función o método son consideradas el flujo principal del programa.

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


Identificadores

Los identificadores en OMI sirven para nombrar elementos del script definidos por el programador, tales como variables, funciones, etiquetas, clases...

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.

Palabras reservadas

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.


Tipos de datos

Haciendo uso del lenguaje OMI se puede operar sobre varios tipos de datos simples y compuestos.

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.


Booleano

Este tipo de dato puede contener dos únicos valores: verdadero o falso. Es el tipo de dato más simple. El valor verdadero se expresa mediante el literal ``true'', mientras que el falso se representa mediante ``false''.

   a = true; // a presenta el valor verdadero
   b = false; // b presenta el valor falso

8#6


Numérico

En OMI únicamente existe un tipo de dato numérico. Mediante este tipo de dato es posible representar números enteros y aquellos decimales que puedan ser expresados en punto flotante.

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


Cadena de caracteres

En general, un carácter se refiere a una letra, número u otro símbolo o signo. Las cadenas de caracteres son una sucesión de estos. Normalmente representan un contenido textual.

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


Array

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


Objetos

En programación orientada a objetos, un objeto es una estructura de datos que presenta un comportamiento asociado. Tiene un estado interno determinado por el valor de los datos que contiene, y se encarga de realizar una serie de tareas en tiempo de ejecución. Una clase de objetos es un modelo que define las propiedades y el comportamiento que tienen objetos afines.

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

Nulo

Nulo representa la ausencia de valor. Es un tipo de dato especial que se utiliza para expresar referencias que no tienen valor asociado. Paradójicamente el valor nulo conlleva la ausencia de valor.

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


Función

En OMI una función hace referencia a un bloque de sentencias que puede recibir una serie de parámetros y tomar un valor. Una función realiza una tarea específica dentro del programa. Cuando una función es llamada se ejecuta el bloque de sentencias al que referencia, pasándole como parámetros los valores en la llamada.

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

Expresión regular

Una expresión regular es un patrón definido por un lenguaje regular. Normalmente se aplica a una cadena de caracteres, para determinar si pertenece al lenguaje que define. Se utiliza para realizar búsquedas de subcadenas o llevar a cabo remplazos.

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


Conversión implícita entre tipos de datos

OMI es un lenguaje de tipado débil, es decir, dado un valor de un tipo concreto es posible utilizarlo como otro tipo sin necesidad de llevar a cabo una conversión de forma explícita. Al usar un dato en una operación que espera otro tipo OMI realizará una conversión automática.

Nulo
Booleano:
false
Numérico:
0
Cadena de caracteres:
13#1113#11
Booleanos
false
Numérico:
0
Cadena de caracteres:
13#1113#11
true
Numérico:
1
Cadena de caracteres:
13#11113#11
Numéricos
Número 0
Booleano:
false
Cadena de caracteres:
13#11013#11
Número distinto a 0
Booleano:
true
Cadena de caracteres:
Cadena que representa el número.
Cadena de caracteres
Cadena vacía
Booleano:
false
Numérico:
0
Cadena numérica
Booleano:
true
Numérico:
Número que representa la cadena
Otra cadena
Booleano:
true
Numérico:
Tamaño de la cadena

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.

Array
Array vacío
Booleano:
false
Numérico:
0
Cadena de caracteres:
13#1113#11
Array con elementos
Booleano:
true
Numérico:
0
Cadena de caracteres:
13#1113#11

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).

Operadores

OMI soporta una serie de operadores de naturaleza muy variada. Mediante un operador se puede construir una expresión que, al ser evaluadas, se aplicará una función sobre un conjunto de datos de entrada denominados operandos, y obtener así un valor.

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.


Operadores lógicos

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:

Negación:
14#12
AND lógico:
15#13, 16#14
OR lógico:
17#15, 18#16

   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


Operadores de comparación

Se encargan de evaluar el orden entre dos valores. El resultado es un valor booleano de verdadero si se satisface la comparación o falso en caso contrario.

Los operadores de comparación son los siguientes:

Igualdad:
20#18
Desigualdad:
21#19
Menor que:
22#20
Menor o igual que:
23#21
Mayor que:
24#22
Mayor o igual que:
25#23
Idéntico:
26#24
No idéntico:
27#25

   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


Operadores aritméticos

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:

Suma:
28#26
Diferencia:
29#27
Producto:
30#28
Division:
31#29
Módulo:
32#30
Potencia:
ˆ

   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


Operadores sobre cadenas de caracteres

OMI soporta una serie de operadores que pueden ser aplicados sobre cadenas de caracteres.

Concatenación:
34#32
Acceso a posición:
35#33

   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


Operadores de asignación

La asignación es la operación mediante la cual el valor de una expresión puede ser atribuido a un símbolo variable. De esta forma se puede hacer uso del valor en otras expresiones. El operador se representa mediante un signo igual ``='' y toma una expresión (operando derecho) para asignarla a un símbolo variable (operando izquierdo).

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.

Suma y asignación:
37#35
Diferencia y asignación:
38#36
Producto y asignación:
39#37
División y asignación:
40#38
Potencia y asignación:
ˆ =
Módulo y asignación:
41#39
Concatenación y asignación:
42#40

   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


Operadores de incremento y decremento

Estos operadores se aplican a una expresión variable, incrementando o decrementado su valor numérico. Este tipo de operaciones implican una asignación.

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.

Preincremento:
44#42
Posincremento:
45#43
Predecremento:
46#44
Posdecremento:
47#45

   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


Operadores de conversión de tipo

En OMI se definen una serie de operadores que permiten realizar una conversión explícita a un determinado tipo de dato.

Booleano:
49#47
Entero:
50#48
Punto flotante:
51#49
Cadena de caracteres:
52#50

   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


Operadores sobre arrays

El lenguaje OMI soporta una serie de operadores que pueden ser aplicados sobre un dato array.

Acceso a posición:
54#52
Acceso a final:
55#53
Tamaño:
56#54

   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


Operadores sobre clases y objetos

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.

Acceso a atributos y métodos:
-24#22
Instanciación de clases:
7#5
Acceso al objeto en ejecución:
58#56
Acceso al objeto padre en ejecución:
59#57
Acceso a la clase en ejecución:
60#58

El uso de las clases y objetos quedan descritos en la sección correspondiente (sec:class).

61#59


Operadores sobre funciones

OMI define una serie de operadores que son aplicables a funciones.

Llamada a función:
62#60
Aplicación parcial:
63#61
Acceso a función de contexto:
64#62

El uso de las funciones queda descritos en la sección correspondiente (sec:function).

65#63


Operadores comprobación de tipos

En OMI existen una serie de operadores encargados de comprobar el tipo de un dato dado. Estos devolverán un valor booleano que dependerá de que el dato sea del tipo indicado o no.

Comprobación de nulo:
66#64
Comprobación de booleano:
67#65
Comprobación de numérico:
68#66
Comprobación de cadena:
69#67
Comprobación de array:
70#68
Comprobación de objeto:
71#69

   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


Operadores condicionales

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.

Operador ternario:
73#71
Fusión de nulos:
74#72

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


Operadores de entrada/salida

OMMI presenta una serie de operadores que permiten leer de la entrada estándar o escribir en la salida estándar.

Entrada estándar:
76#74, 77#75
Salida estándar:
78#76, 79#77 , 80#78

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


Precedencia de operadores

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:

Llamadas:
81#79, 7#5
Operadores de acceso:
55#53, -24#22, 64#62, 58#56, 60#58
Incrementos y decrementos:
82#80, 83#81
Conversión de tipos:
49#47, 50#48, 51#49, 52#50
Operadores condicionales:
84#82, 85#83
Operaciones aritméticas de primer nivel:
32#30, ˆ
Operaciones aritméticas de segundo nivel:
30#28, 31#29
Operaciones aritméticas de tercer nivel:
28#26, 29#27
Operaciones comparación:
20#18, 21#19, 22#20, 23#21, 24#22, 25#23, 26#24, 27#25
Operaciones lógicas de primer nivel:
14#12
Operaciones lógicas de segundo nivel:
15#13
Operaciones lógicas de tercer nivel:
17#15
Asignaciones:
86#84, 37#35, 38#36, 39#37, 40#38, 41#39, 42#40, ˆ =

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.


Etiquetas

Es posible etiquetar sentencias dentro del código fuente para poder cambiar el flujo de ejecución al punto que esta ocupa.

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

Sentencias de control

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.


Sentencia condicional 89#87

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
   }

Sentencia condicional 93#91

La sentencia 93#91 agiliza la toma de decisiones múltiples. Opera de igual forma que varios 89#87 anidados, pero presenta una sintaxis que en muchos casos favorece la legibilidad y la rapidez de programación.

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


Sentencia iterativa 97#95

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.


Sentencia iterativa 98#96

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.


Sentencia iterativa 99#97

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.


Sentencia iterativa 100#98 y 101#99

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.

Booleano:
Si es true se ejecutará el bloque indefinidamente y el símbolo variable tendrá el valor true. Si es falso el bloque de sentencias no se ejecutará.
Numéricos:
Si es mayor que se 0 el bloque se ejecutará desde 0 hasta el valor de la expresión y el símbolo variable tendrá el valor numérico de cada iteración. En otro caso no se llega a ejecutar el bloque.
Cadenas de caracteres:
Se ejecuta el bloque de sentencias por cada carácter en la cadena, el símbolo variable tomará como valor la cadena correspondiente al caracter de cada iteración.
Array:
Se ejecuta el bloque de sentencias por cada elemento en el array, el símbolo variable tomará como valor el elemento de cada iteración.

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; 
   }


Sentencia iterativa ágil

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.


Sentencia de salto 94#92

La sentencia 94#92 permite terminar la ejecución de un bloque de sentencias correspondientes a una estructura iterativa y saltar a la siguiente sentencia tras el bloque.

   /*
      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";


Sentencia de salto 102#100

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";


Sentencia de salto 87#85

La sentencia 87#85 se compone de un identificador y permite cambiar el flujo de ejecución a la sentencia etiquetada con dicho identificador.

   /*
      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


Sentencia 104#102

La sentencia 104#102 permite separar el código fuente en diferentes ficheros. Se compone de una expresión que se corresponde con el fichero a incluir. Al ser evaluada, la ejecución pasa a la primera sentencia en el fichero.

   /*
      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.


Sentencia 105#103

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.


Sentencia 106#104

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
   }


Sentencia 107#105

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


Sentencia 110#108

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


Sentencia 113#111

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";


Sentencia 114#112

La sentencia 114#112 imprime en la salida estándar el tipo de dato del valor referenciado por un símbolo variable.

   foo = 10;
   typeof foo; // Imprime Arithmetic
   foo = "STR";
   typeof foo; // Imprime String
   foo = {1,2};
   typeof foo; // Imprime Array(2)


Sentencia 115#113

La sentencia 115#113 permite consultar el estado interno de un dato. Esto es la posición de memoria que ocupa, el tipo y el número de referencias que tiene.

   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.

Variables

En programación una variable se define como un espacio que es asociado a un nombre o identificador, el contenido alojado en dicho espacio es llamado el valor de la variable. Mediante el nombre es posible utilizar la variable en expresiones con independencia de la información exacta que esta referencia. Cuando una expresión que contiene una variable es resuelta se obtiene el valor contenido en la misma.

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

Referencias

Una referencia es un valor que permite acceder indirectamente a un dato almacenado en un símbolo variable. Cuando se opera sobre una referencia se hace sobre el dato almacenado en la variable.

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


Funciones

Una función es un conjunto de sentencias que tienen un objetivo particular. Se corresponde con un subalgoritmo dentro del algoritmo principal.

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.

Parámetros con valores por defecto

En OMI es posible definir funciones cuyos parámetros presenten un valor por defecto, es decir, en caso de ser omitido en una llamada dicho parámetro tendrá el valor especificado.

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


Paso de parámetros por referencia

Por defecto los parámetros de una llamada a función son pasados por valor, lo que implica que cualquier modificación en los mismos dentro de la función no tendrá efecto fuera de esta.

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.

Definición como parte de expresiones

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"

Referencias a funciones

En OMI se nombran de igual forma a las funciones, las variables y las clases. Para determinar si un identificador en una expresión hace referencia a una función u otro elemento se utiliza el contexto.

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:

Funciones anónimas

OMI permite definir funciones sin identificador que las nombre, lo que se denomina funciones anónimas. Normalmente las funciones anónimas son utilizadas como dato en otras expresiones.

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";

Expresiones parametrizadas

p Una expresión parametrizada es similar a una función. Permite definir una expresión a partir de unos parámetros.

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);

Funciones de orden superior

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"

Clausura de funciones

Si una función es definida en el bloque de sentencias de otra, cuando la función interna sea llamada, en su ejecución podrá acceder a las variables definidas en el bloque en el momento en el que se definió.

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

Función de contexto

Por regla general la función de contexto se refiere a la función en ejecución. En OMI es posible acceder a la función de contexto mediante el operador ``64#62'', esto permite definir funciones recursivas de forma simple.

   ~ 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.

Decoradores

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");

Aplicación parcial

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

Listas por comprensión

En OMI es posible definir una lista o un diccionario por compresión. Para ello se utiliza una construcción del lenguaje similar a la sentencia iterativa 126#124, salvo que en cada iteración genera un elemento del array asociado a la lista o diccionario.

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:

Booleano:
Se itera hasta que se evalúa como falso. En cada iteración se asigna el entero correspondiente al número de la misma.
Numérico:
Si es positivo se itera desde cero hasta el valor sin incluirlo. Si es negtivo no se itera.
Cadena de caracteres:
Se itera por cada carácter en la cadena.
Array:
Se itera por cada elemento en el array.

   /*
      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 !== "" );


Clases de objeto

OMI es un lenguaje orientado a objetos, por tanto permite hacer uso de los conceptos y recursos enmarcados dentro de este paradigma de programación.

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.

Accesibilidad de métodos y atributos

Los métodos y atributos definidos en un clase pueden tener restricciones a la accesibilidad de los mismos. Estos pueden ser públicos o privados.

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.

Atributos y 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});
   }

Herencia de clases

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.

Redefinición de clases

En OMI es posible redefinir una clase, de forma que se pueden añadir métodos o sobrescribir los existentes.

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.

Clases bases

En OMI existen una serie de clases denominadas clases bases, a partir de las cuales se definen los tipos de datos básicos. Estas son:

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.

Duck typing

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.


Métodos mágicos

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:

_str:
Se llama cuando se accede a un objeto como una una cadena de caracteres.
_get:
Se llama cuando se accede a un atributo que no existe.
_call:
Se llama cuando se accede a un método que no existe.

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"


Entorno del programa

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


Excepciones

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.

Reflexión

OMI es un lenguaje que presenta características reflexivas. Un programa escrito en OMI puede hacer uso del entorno construido durante su ejecución para cambiar su propia estructura y comportamiento.

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

Introspección de tipos

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"


Errores

Cuando un programa escrito en OMI es interpretado se pueden dar diferentes tipos de errores. Un error de interpretación puede tener diferentes efectos, pero en cualquier caso se presentará información sobre el error ocurrido. Si el código fuente está contenido en un fichero se indicará el nombre del mismo y la línea de código en la que se ha producido el error.

Léxicos

Los errores léxicos son originados durante el procesado del código fuente, cuando existe un carácter o cadena que no es reconocida por el intérprete. En este caso se detendrá el programa no será ejecutado y fallará el proceso de interpretación.

   |æ| = 10;
   Error: lexical error, unexpected character (|æ|)

Sintácticos

Los errores sintácticos tienen lugar durante el procesado del código fuente, cuando una sentencia o expresión no está construida de forma correcta y no respeta las reglas establecidas por el lenguaje. Si se detecta un error sintáctico al procesarse el código fuente este no será ejecutado y el programa se detendrá.

   [ id );
   Error: syntax error, unexpected |')'|, expecting |','| or |']'|

Semánticos

Los errores semánticos se dan durante la ejecución del programa. Estos son dependientes del entorno y los datos.

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.

Funciones del lenguaje

Cadenas de caracteres

OMI pone a disposición del programador un conjunto de funciones que operan sobre cadenas de caracteres.

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.

str_explode

La función str_explode divide en partes una cadena de caracteres según una subcadena denominada separador. El resultado será un array de elementos que contendrá las cadenas correspondientes a cada una de las partes.


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 (",");

str_find

La función str_find obtiene la primera posición de una subcadena dentro de una cadena de caracteres.


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

str_replace

La función str_replace busca las ocurrencias de una subcadena dada en una cadena de caracteres, sustituyéndolas por una cadena de remplazo. El valor devuelto por la función es la cadena con los remplazos efectuados.


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");

str_replace_sub

La función str_replace_sub remplaza la subcadena dada por una posición inicial y una longitud dentro de un cadena de caracteres por otra cadena.


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");

str_upper

La función str_upper convierte todos los caracteres alfabéticos de una cadena en mayúsculas.


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"

str_lower

La función str_lower convierte todos los caracteres alfabéticos de una cadena en minúsculas.


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"

str_search

La función str_search permite llevar a cabo una búsqueda aplicando un patrón sobre una cadena de texto. Se obtendrá un array con los resultados de la búsqueda.


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.

str_match

La función str_match comprueba si una cadena de caracteres pertenece o no al lenguaje definido por una expresión regular.


147#145

   
   foo = str_match ("ABCD",`A[^D]*D`); // foo vale true
   foo = "ABCD"->match(`A[^D]*D`); // foo vale true

regexp

La función regexp convierte una cadena de caracteres dada en una expresión regular


148#146

   
   pattern = regexp ("A[^D]*D"); 
   foo = "ABCD"->match(pattern); // foo vale true

sprintf

La función sprintf permite obtener una cadena de caracteres formateada a partir de una serie de valores.


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("%%%%%%"); // %%%%

Arrays

OMI pone a disposición del programador un conjunto de funciones que operan sobre arrays.

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.

array_implode

Forma una cadena de caracteres a partir de un array de cadenas y una cadena separadora.


150#148

   
   << array_implode ({"Hola", "Mundo"}, " "); // Imprime "Hola Mundo"
   << {"Hola", "Mundo"}->implode (" "); // Imprime "Hola Mundo"

array_first

Obtiene el primer elemento de un array.


151#149

   
   << array_first ({"Hola", "Mundo"}); // Imprime "Hola"
   << {"Hola", "Mundo"}->first (); // Imprime "Hola"

array_last

Obtiene el último elemento de un array.


152#150

   
   << array_last ({"Hola", "Mundo"}); // Imprime "Mundo"
   << {"Hola", "Mundo"}->last (); // Imprime "Mundo"

array_insert

Inserta un elemento en una determinada posición de un array dado.


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"}

array_delete

Elimina el elemento ocupa una determinada posición en un array dado.


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' }

array_push

Inserta un elemento al final de un array dado.


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' }

array_pop

Elimina y devuelve el último elemento de un array dado.


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' }

array_unshift

Inserta un elemento al inicio de un array dado.


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' }

array_shift

Elimina y devuelve el primer elemento de un array dado.


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' }

array_reduce

Lleva a cabo la reducción de un array a un único valor, aplicando iterativamente una función.


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;
      }
   );

Procesos

OMI proporciona una serie de funciones para gestionarlos procesos. Así es posible abrir varios hilos de ejecución creando procesos.

Es posible enviar señales a los procesos creados, y estos podrán manejarlas según sea necesario.

exec

Ejecuta un comando del sistema y devuelve la salida.


160#158

   
   /*
    * foo vale el listado de procesos en ejecución
    */
   foo = exec ('ps -e');

fork

Crea un proceso hijo del proceso en ejecución.


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";

wait

Espera la finalización de todos o algunos de los procesos hijos creados.


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";
   }

getpid

Obtiene el identificador del proceso actual.


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
   }

getppid

Obtiene el identificador del proceso padre.


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
   }

signal

Envía una señal a un proceso.


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";
   }

shandler

Permite establecer una función para manejar señales enviadas al proceso actual.


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";
      }
   }

exit_process

Finaliza el proceso en ejecución y todos los hijos de este. Si se da en el proceso principal se sale del programa.


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";
   }

process

Realiza una llamada a función como un proceso.


168#166

  
   process ( ~() { $(100) << "P1:".$; } );
   process ( ~(i) { $(i) << "P2:".$; }, 100 );

Ficheros

OMI dispone de una serie de funciones para gestionar ficheros. Estas funciones permiten abrir un fichero, leer su contenido, escribir en el mismo, etc.

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.

file

Abre un fichero según el modo especificado.


169#167

  
   if (f = file ("example.file", "w+")){
      << "Fichero abierto con é|xito";
   } else {
      << "Error abriendo el fichero";
   }

fclose

Cierra un fichero abierto mediante la función ``file''


170#168

  
   if (f = file ("example.file", "w+")){
      fclose (f); 
   } else {
      << "Error abriendo el fichero";
   }

fput

Escribe un contenido dado en la posición de lectura/escritura a la que apunta un puntero a fichero. Esta función presenta un alias ``f>:''.
171#169

  
   if (f = file ("example.file", "w+")){
      f>: (f, "Contenido ha escribir"); 
      fclose(f);
   } else {
      << "Error abriendo el fichero";
   }

fget

Lee el un número de caracteres de un fichero, a partir de la posición de lectura/escritura dada por el puntero al mismo. Esta función presenta un alias ``f<:''.


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";
   }

fseek

Cambia la posición de lectura/escritura de un puntero a 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";
   }

ftell

Obtiene la posición de lectura/escritura a la que apunta un puntero a 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";
   }

fread

Lee el contenido de un fichero.


175#173

  
   foo = fread ('/etc/hosts'); // foo vale el contenido del fichero /etc/hosts

fwrite

Escribe el contenido de un fichero.


176#174

  
   /*
   * El fichero /etc/hosts queda
   * con el contenido:
   * 127.0.0.1 localhost
   */
   fwrite ('/etc/hosts', '127.0.0.1 localhost');

fappend

Escribe el contenido al final de un fichero.


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');

Fechas y tiempo

OMI dispone de un conjunto de funciones de tiempo que permiten operar con fechas y con marcas de tiempo.

date

Obtiene una fecha formateada.


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

time

Obtiene la marca de tiempo Unix actual.


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.

Extensiones del lenguaje

OMI es un lenguaje modular que puede ser extendido. Los módulos o extensiones permiten añadir funciones al lenguaje, ampliando así su funcionalidad y uso.

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).

Carga de módulos

Para cargar un módulo desde el código fuente se puede utilizar la sentencia 180#178 seguida de una cadena entre paréntesis que representa la biblioteca dinámica que se usará como módulo.

  
   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.

Creación de módulos

OMI permite al programador extender el lenguaje haciendo uso de una biblioteca para el desarrollo de módulos. Esta dispone de los elementos sobre los que se construye el intérprete.

Para construir un módulo se ha de disponer de la biblioteca OMI correctamente instalada. Para ello se dispone de dos opciones:

Paquete .deb:
http://wwww.omi-project.com/download/deb/libomi-dev_$VERSION.deb
Código fuente:
A partir del código fuente de OMI se puede construir e instalar la biblioteca mediante las reglas 181#179 e install-dev

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"

Arquitectura cliente/servidor

El intérprete OMI puede funcionar bajo una arquitectura cliente/servidor para ello solo se ha de ejecutar este con la opción ``-s'' y el puerto de escucha.

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.

Descripción del proceso de interpretación

Es posible iniciar el intérprete de forma que produzca una salida en formato JSON que describa los procesos de interpretación llevados a cabo. La estructura de datos JSON devuelta es descrita en su sección correspondiente de diseño del software.

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.

runTree

runTree es un cliente para el intérprete OMI. Lee la estructura JSON producida por este y la representa en una interfaz gráfica.

La interfaz de runTree se divide como sigue:

Sobre este documento...

Image logo-doc

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