2. Definición del lenguaje


2.1 Objetos


2.1.1 Atomos y secuencias

Todos los objetos de datos en Euphoria son tanto átomos como secuencias. Un átomo es un valor numérico simple. Una secuencia es una colección de valores numéricos.

Los objetos contenidos en una secuencia pueden ser una mezcla arbitraria de átomos o secuencias. Una secuencia se representa por una lista de objetos entre llaves, separados por comas. Los átomos pueden tener un entero o un valor de punto flotante de doble presición. Tienen un rango aproximado desde -1e300 (menos uno por 10 a la 300) hasta +1e300 con 15 decimales de exactitud. Aquí están algunos objetos Euphoria:

        -- ejemplos de átomos:
        0
        1000
        98.6
        -1e6

        -- ejemplos de secuencias:
        {2, 3, 5, 7, 11, 13, 17, 19}
        {1, 2, {3, 3, 3}, 4, {5, {6}}}
        {{"juan", "perez"}, 52389, 97.25}
        {}                        -- la secuencia de 0 elementos

Los números se pueden ingresar también en hexadecimal. Por ejemplo:

        #FE             -- 254
        #A000           -- 40960
        #FFFF00008      -- 68718428168
        -#10            -- -16

Solamente las letras mayúsculas A, B, C, D, E, F están permitidas en los números hexadecimales.

Las secuencias se pueden anidar a cualquier profundidad, es decir, puede tener secuencias dentro de secuencias dentro de secuencias y así hasta cualquier profundidad (hasta que se agote la memoria). Las llaves se usan para construir secuencias out of a lest of expressions. Esas expresiones pueden ser constantes o ser evaluadas en tiempo de ejecución, por ejemplo:

        {x+6, 9, y*w+2, sin(0.5)}

La parte "Objetos Jerárquicos" del acrónimo Euphoria viene de la naturaleza jerárquica de las secuencias anidadas. Esto no debería ser confundido con las jerarquías de clases de ciertos lenguajes orientados a objetos.

¿Por qué los llamamos átomos? ¿Por qué no solamente "números"? Bien, un átomo es sólo un número, pero queríamos tener un término distintivo que enfatizara que son indivisibles. Por supuesto, en el mundo de la Física, los átomos se puedieron separar en partes más pequeñas hace algunos años atrás, pero en Euphoria no se pueden separar. Son los bloques básicos de todos los datos que un programa Euphoria puede manejar. Con esta analogía, las secuencias tendrían que ser las "moléculas", hechas de átomos y otras moléculas. Una analogía mejor podría ser que las secuencias son como los directorios y los átomos como los archivos. Como un directorio en su computadora puede contener tanto archivos como otros directorios, una secuencia puede contener tanto átomos como otras secuencias (y esas secuencias pueden contener átomos y secuencias, etc.).

Como descubrirá dentro de poco, las secuencias hacen a Euphoria muy simple y muy potente. Comprender los átomos y las secuencias es la clave para comprender Euphoria.

Nota de rendimiento:
¿Esto significa que todos los átomos se almacenan en memoria como números de punto flotante de 8 bytes? No. El intérprete Euphoria normalmente almacena átomos de valor entero como los enteros de la máquina (4 bytes) para ahorrar especio y mejorar la velocidad de ejecución. Cuando el resultado es fraccionario o se obtienen números demasiado grandes, la conversión a punto flotante ocurre automáticamente.

2.1.2 Cadenas de caracteres y caracteres individuales

Una cadena de caracteres es solamente una secuencia de caracteres. Se puede ingresarla usando comillas, por ejemplo:

        "ABCDEFG"

Las cadenas de caracteres se pueden manipular y operar igual que cualquier otra secuencia. Por ejemplo, la cadena del ejemplo es completamente equivalente a la secuencia:

        {65, 66, 67, 68, 69, 70, 71}
que contiene los correspondientes códigos ASCII. El compilador Euphoria convertirá inmediatamente "ABCDEFG" a la secuencia anterior de números. En un sentido, no hay "cadenas" en Euphoria, solamente secuencias de números. Una cadena entrecomillada es realmente una notación conveniente que le evita tener que escribir todos los códigos ASCII.

Esto "" es equivalente a {}. Ambos representan la secuencia de longitud 0, también conocida como secuencia vacía. Como una cuestión de estilo de programación, es natural usar "" para sugerir una secuencia de caracteres de longitud 0, y {} para indicar alguna otra clase de secuencia.

Un caracter individual es un átomo. Tiene que ingresarse con comillas simples. Hay diferencia entre un caracter individual (que es un átomo), y una cadena de caracteres de longitud 1 (que es una secuencia), por ejemplo:

        'B'   -- equivalente al átomo 66 - el código ASCII de la letra B
        "B"   -- equivalente a la secuencia {66}

Otra vez, 'B' es sólo una notación que es lo mismo que escribir 66. Realmente no hay ningún "caracter" en Euphoria, sólo números (átomos). Tenga presente que un átomo no es equivalente a una secuencia de un elemente conteniendo el mismo valor, aunque hay muy pocas rutinas internas que eligen tratarlos similarmente.

Los caracteres especiales se tienen que ingresar usando una barra invertida:

        \n        nueva línea
        \r        retorno de carro
        \t        tabulador
        \\        barra invertida
        \"        comilla doble
        \'        comilla simple

Por ejemplo, "Hola, Mundo!\n", o '\\'. El editor Euphoria muestra las cadenas de caracteres en verde.


2.1.3 Comentarios

Los comentarios empiezan con dos guiones y se extienden hasta el final de la línea, por ejemplo:

        -- esto es un comentario

El compilador ignora todos los comentarios, los cuales no afectan la velocidad de ejecución. El editor muestra los comentarios en rojo.

En la primera línea (solamente) de su programa, puede usar un comentario especial, que comienza con #!, por ejemplo:

        #!/home/rob/euphoria/bin/exu  
Esto le dice al shell de Linux que su programa debería ser ejecutado por el intérprete de Euphoria, dándole la ruta completa del intérprete. Si hace ejecutable su programa, puede correrlo solamente escribiendo su nombre, sin necesidad de escribir"exu". En DOS y Windows estas líneas se tratan como un comentario.



2.2 Expresiones

Como otros lenguajes de programación, Euphoria le permite calcular resultados al formar expresiones. Sin embargo, en Euphoria puede realizar cáculos sobre secuencias de datos con una expresión, donde en la mayoría de los lenguajes debería construir un ciclo. En Euphoria puede manejar una secuencia como si se tratase de un simple número. Se la puede copiar, pasar a una subrutina o calcular como una unidad. Por ejemplo:

        {1,2,3} + 5
es una expresión que suma la secuencia {1,2,3} y el átomo 5 para obtener la secuencia resultante {6,7,8}. Verá más ejemplos más tarde.


2.2.1 Operadores relacionales

Los operadores relacionales <   >   <=   >=   =   !=   producen resultados de 1 (verdadero) o de 0 (falso).

        8.8 < 8.7   -- 8.8 menor que 8.7 (falso)
        -4.4 > -4.3 -- -4.4 mayor que -4.3 (falso)
        8 <= 7      -- 8 menor o igual a 7 (falso)
        4 >= 4      -- 4 mayor o igual a 4 (verdadero)
        1 = 10      -- 1 igual a 10 (falso)
        8.7 != 8.8  -- 8.7 no es igual a 8.8 (verdadero)

Como veremos pronto, estos operadores se pueden aplicar a las secuencias.


2.2.2 Operadores lógicos

Los operadores lógicos and, or, xor, y not se usan para determinar el valor de "verdad" de una expresión, por ejemplo:

        1 and 1     -- 1 (verdadero)
        1 and 0     -- 0 (falso)
        0 and 1     -- 0 (falso)
        0 and 0     -- 0 (falso)

        1 or  1     -- 1 (verdadero)
        1 or  0     -- 1 (verdadero)
        0 or  1     -- 1 (verdadero)
        0 or  0     -- 0 (falso)

        1 xor 1     -- 0 (falso)
        1 xor 0     -- 1 (verdadero)
        0 xor 1     -- 1 (verdadero)
        0 xor 0     -- 0 (falso)

        not 1       -- 0 (falso)
        not 0       -- 1 (verdadero)

También puede aplicar estos operadores a otros números que no sean 1 o 0. La regla es: cero significa falso y no cero es verdadero. Entonces, por ejemplo:

        5 and -4    -- 1 (verdadero)
        not 6       -- 0 (falso)

Estos operadores también se aplican a secuencias. Ver debajo. En algunos casos la evaluación de corto-circuito se usará para expresiones que contienen and u or.


2.2.3 Operadores aritméticos

Los operadores aritméticos disponibles son: suma, resta, multiplicación, división, menos unario y más unario.

        3.5 + 3  -- 6.5
        3 - 5    -- -2
        6 * 2    -- 12
        7 / 2    -- 3.5
        -8.1     -- -8.1
        +8       -- +8

Al calcular un resultado que es demasiado grande (es decir, fuera del rango -1e300 a +1e300) aparecerá uno de los átomos especiales +infinity o -infinity. Aparecen como inf o -inf cuando los imprime. También es posible generar un nan o -nan. "nan" significa "no es un número" (del inglés, "not a number"), es decir, un valor indefinido (como inf dividido inf). Estos valores están definidos en el estándar de punto flotante del IEEE. Si ve uno de estos valores especiales en la salida de su programa, normalmente indica un error en la lógica de su programa, aunque generar inf como resultado intermedio puede ser aceptable en algunos casos. Por ejemplo, 1/inf es 0, que puede ser la respuesta "correcta" para su algoritmo.

La división por cero, tanto como argumentos erróneos en rutinas de librería, por ejemplo, raíz cuadrada de un número negativo, logaritmo de un número no positivo, etc, causará un mensajede error inmediato y la cancelación del programa. La única razón por la que querría usar el "más unario", sería para enfatizar la lectura de un número (como valor positivo). El intérprete realmente no calcula nada con ellos.


2.2.4 Operaciones sobre secuencias

Todos los operadores relacionales, lógicos y aritméticos descriptos antes, como las rutinas matemáticas descriptas en Parte II - Rutinas de Librería, se pueden aplicar tanto a las secuencias como a los números simples (átomos).

Al aplicar un operador unario (un operando) a una secuencia, realmente se aplica a cada elemento de la secuencia para producir una secuencia de resultados de la misma longitud. Si uno de esos elementos es en sí mismo una secuencia, entonces la misma regla se aplica recursivamente, por ejemplo:

        x = -{1, 2, 3, {4, 5}}   -- x es {-1, -2, -3, {-4, -5}}

Si un operador binario (dos operandos) tiene operandos que ambos son secuencias, entonces las dos secuencias deben tener la misma longitud. La operación binaria se aplica entonces a los elementos correspondientes tomados de las dos secuencias, y obteniendo una secuencia de resultados, por ejemplo:

        x = {5, 6, 7, 8} + {10, 10, 20, 100}
        -- x es {15, 16, 27, 108}

Si el operador binario tiene un operando que es una secuencia, mientras que el otro es un número simple (átomo), entonces el número simple se repite para formar una secuencia de igual longitud que la secuencia operando. Entonces se aplican las reglas para operaciones sobre dos secuencias. Algunos ejemplos:

        y = {4, 5, 6}

        w = 5 * y              -- w es {20, 25, 30}

        x = {1, 2, 3}

        z = x + y              -- z es {5, 7, 9}

        z = x < y              -- z es {1, 1, 1}

        w = {{1, 2}, {3, 4}, {5}}

        w = w * y              -- w es {{4, 8}, {15, 20}, {30}}

        w = {1, 0, 0, 1} and {1, 1, 1, 0}    -- {1, 0, 0, 0}

        w = not {1, 5, -2, 0, 0}     -- w es {0, 0, 0, 1, 1}

        w = {1, 2, 3} = {1, 2, 4}    -- w es {1, 1, 0}
        
-- observe que el primer '=' es de asignación, y el segundo '=' es un operador relacional que prueba la igualdad
Nota: Cuando desee comparar dos cadenas (u otras secuencias) , no debería (como en algún otro lenguaje) usar el operador '=':
       if "APPLE" = "ORANGE" then  -- ERROR! 
'=' es tratado como un operador, como '+', '*' etc., por lo que se aplica a los elementos correspondientes de las secuencias, teniendo que ser éstas de la misma longitud. Cuando sus longitudes son iguales, el resultado es una secuencia de 1 y 0. Cuando las longitudes no son las mismas, el resultado es un error. De cualquiera forma obtendrá un error, since la condición if tiene que ser un átomo, no una secuencia. En su lugar, debería usar la rutina interna equal():
       if equal("APPLE", "ORANGE"} then  -- CORRECTO
En general, puede hacer comparaciones relacionales usando la rutina interna compare():
       if compare("APPLE", "ORANGE"} = 0 then  -- CORRECTO
Puede usar compare() para otras comparaciones como:
       if compare("APPLE", "ORANGE"} < 0 then  -- CORRECTO
           -- entrar aquí si "APPLE" es menor que "ORANGE" (verdadero)


2.2.5 Indexación de secuencias

Se puede seleccionar un solo elemento de una secuencia al darle un número de elemento encerrado entre corchetes. Los números de elemento comienzan en 1. Los índices no enteros se redondean hacia abajo al entero más próximo.

Por ejemplo, si 'x' contiene {5, 7.2, 9, 0.5, 13}, entonces x[2] es 7.2. Suponga que asignamos otra cosa a x[2]:

        x[2] = {11,22,33}

Entonces 'x' se convierte en: {5, {11,22,33}, 9, 0.5, 13}. Si ahora preguntamos por x[2], obtendremos {11,22,33} y si preguntamos por x[2][3] obtendremos el 33. Si intenta utilizar un índice que está fuera del rango 1 a cantidad de elementos, obtendrá un error de índice. Por ejemplo, x[0], x[-99] o x[6] causarán errores. También lo hará x[1][3], ya que x[1] no es una secuencia. No hay límite en la cantidad de índices que pueden seguir a una variable, pero la variable tiene que contener secuencias que estén anidadas con la suficiente profundidad. El array de dos dimensiones, común en muchos lenaguajes, se puede representar fácilmente con una secuencia de secuencias:

        x = {
             {5, 6, 7, 8, 9},      -- x[1]
             {1, 2, 3, 4, 5},      -- x[2]
             {0, 1, 0, 1, 0}       -- x[3]
            }
en donde escribimos los números de forma tal que resulte más clara la estructura. Se puede usar una expresión de la forma x[i][j] para acceder a cualquier elemento.

Sin embargo, las dos dimensiones no son simétricas, ya que se puede seleccionar una "fila" entera con x[i], pero no hay una expresión sencilla para seleccionar una columna entera. También se pueden manejar fácil y flexiblemente otras estructuras lógicas, como arrays de n dimensiones, arrays de cadenas, estructuras, arrays de estructuras, etc:

 array 3-D:
        y = {
             {{1,1}, {3,3}, {5,5}},
             {{0,0}, {0,1}, {9,1}},
             {{-1,9},{1,1}, {2,2}}
            }
y[2][3][1] es 9

Array de cadenas:

        s = {"Hola", "Mundo", "Euphoria", "", "El único"}
s[3] es "Euphoria"
s[3][1] es 'E'

Una estructura:

        empleado = {
                    {"Juan","Perez"},
                    45000,
                    27,
                    185.5
                   }
Para acceder a los "campos" o elementos dentro de una estructura, es un buen estilo de programación hacer un conjunto de constantes con los nombres de los campos. Esto hace que el programa sea más fácil de leer. Por ejemplo, arriba tendría que tener:
        constant NOMBRE = 1
        constant PRIMER_NOMBRE = 1, APELLIDO = 2

        constant SALARIO = 2
        constant EDAD = 3
        constant ALTURA = 4

Entonces podría acceder al nombre de una persona con: empleado[NOMBRE], o si quiere el apellido podría decir: empleado[NOMBRE][APELLIDO].

Array de estructuras:

        empleados = {
                     {{"Juan","Perez"}, 45000, 27, 185.5},   -- a[1]
                     {{"José","García"}, 57000, 48, 177.2},   -- a[2]

                     -- .... etc.
                    }
empleados[2][SALARIO] would be 57000.

Las estructuras de datos Euphoria son casi infinitamente flexibles. Los arrays en otros lenguajes están restrigidos a tener una cantidad fija de elementos, donde todos ellos tienen que ser del mismo tipo. Euphoria elimina ambas restricciones. Puede agregar fácilmente una nueva estructura a la secuencia empleado de más arriba, o almacenar un inusual nombre largo en el campo NOMBRE y Euphoria se hará cargo de esto por usted. Si desea, puede almacenar una variedad de distintas "estructuras" empleado, con diferentes tamaños, dentro de una sola secuencia.

Un programa Euphoria no solamente puede representar fácilmente todas las estructuras de datos convencionales, sino que puede crear estructuras muy útiles y flexibles, que serían extremadamente complicadas para declarar en otros lenguajes. Ver 2.3 Euphoria vs. lenguajes convencionales.

Advierta que las expresiones en general no puede indexarse, solamente las variables. Por ejemplo: {5+2,6-1,7*8,8+1}[3] no está soportado, ni algo como: date()[MONTH]. Tiene que asignar la secuencia devuelta por date() a una variable, para entonces poder obtener el mes, usando un índice.


2.2.6 Subrangos en secuencias

Se puede seleccionar una secuencia de elementos consecutivos, dándole los números de elemento inicial y final. Por ejemplo, si 'x' es {1, 1, 2, 2, 2, 1, 1, 1}, entonces x[3..5] es la secuencia {2, 2, 2}. x[3..3] es la secuencia {2}. También está permitido x[3..2]. Evalúa la secuencia de longitud 0 {}. Si 'y' tiene el valor {"alfredo", "pedro", "maría"}, entonces y[1..2] es {"alfredo", "pedro"}.

También podemos usar subrangos para sobreescribir porciones de variables. Después de x[3..5] = {9, 9, 9} 'x' sería {1, 1, 9, 9, 9, 1, 1, 1}. También podríamos haber dicho x[3..5] = 9 con el mismo efecto. Suponga que 'y' es {0, "Euphoria", 1, 1}. Entonces, y[2][1..4] es "Euph". Si decimos y[2][1..4]="ABCD", entonces 'y' se convertirá en {0, "ABCDoria", 1, 1}.

En general, un nombre de variable puede estar seguido por 0 o más índices, seguido a su vez por 0 o 1 subrangos. Solamente las variables pueden tener índices o subrangos, no así las expresiones.

Necesitamos ser un poco más precisos definiendo las reglas para los subrangos vacíos. Considere el subrango s[i..j], donde 's' es de longitud 'n'. Un subrango de i a j, donde j = i-1 y i >= 1 produce la secuencia vacía, aún si i = n+1. Así [1..0] y [n+1..n] y todos entre ambos, son subrangos (vacíos) permitidos. Los subrangos vacíos son absolutamente útiles en muchos algoritmos. Un subrango de i a j, donde j < i - 1 es ilegal , es decir, un subrango "inverso" tal como s[5..3], no está permitido.


2.2.7 Concatenación de secuencias y átomos - El operador &

Dos objetos cualquiera se pueden concatenar usando el operador &. El resultado es una secuencia con una longitud igual que las suma de las longitudes de los respectivos objetos (donde se puede considerar que los átomos tienen longitud 1), por ejemplo:

        {1, 2, 3} & 4              -- {1, 2, 3, 4}

        4 & 5                      -- {4, 5}

        {{1, 1}, 2, 3} & {4, 5}    -- {{1, 1}, 2, 3, 4, 5}

        x = {}
        y = {1, 2}
        y = y & x                  -- y es aún {1, 2}

Puede borrar el elemento 'i' de cualquier secuencia 's', al concatenar las partes de la secuencia anteriores y posteriores a 'i':

    s = s[1..i-1] & s[i+1..length(s)]
Esto funciona tanto 'i' sea 1, como length(s), ya que e s[1..0] es un legal rango vacío, y por ende s[length(s)+1..length(s)] también lo es.


2.2.8 Formación de secuencias

Finalmente, la formación de secuencias, usando llaves y comas:

        {a, b, c, ... }
también es un operador. Toma 'n' operandos, donde n es 0 o más, y construye una secuencia de 'n' elementos con sus valores, por ejemplo:
        x = {apple, orange*2, {1,2,3}, 99/4+foobar}

El operador de formación de secuencias, está listado al final del cuadro de precedencias.


2.2.9 Otras operaciones sobre secuencias

Algunas otras importantes operaciones que puede realizar sobre secuencias tienen nombres en inglés, en lugar de caracteres especiales. Estas operaciones están incorporadas en ex.exe/exw.exe/exu, por lo que siempre estarán presentes y también serán rápidas. Se describen en detalle en Parte II - Rutinas de Librería, pero son lo suficientemente importantes para la programación en Euphoria, que las mencionaremos aquí, antes de seguir. Se las llama como si fueran subrutinas, aunque están realmente implementadas de una forma mucho más eficiente que ellas.


length(s)

length() le dice la longitud de una secuencia 's', que es la cantidad de elementos en 's'. Algunos de sus elementos pueden ser secuencias que contienen sus propios elementos, pero length() solamente le da el recuento del "nivel más alto". Obtendrá un error si pide la longitud de un átomo, por ejemplo:

        length({5,6,7})             -- 3
        length({1, {5,5,5}, 2, 3})  -- 4 (no 6!)
        length({})                  -- 0
        length(5)                   -- error!

repeat(item, count)

repeat() construye una secuencia que consta de un 'item' repetido 'count' veces, por ejemplo:

        repeat(0, 100)         -- {0,0,0,...,0}   es decir, 100 ceros
        repeat("Hola", 3)     -- {"Hola", "Hola", "Hola"}
        repeat(99,0)           -- {}

El ítem a repetirse puede ser cualquier átomo o secuencia.


append(s, item) / prepend(s, item)

append() crea una nueva secuencia, agregando un 'item' al final de la secuencia 's'. prepend() crea una nueva secuencia, agregando un 'item' al comienzo de la secuencia 's', por ejemplo:

        append({1,2,3}, 4)         -- {1,2,3,4}
        prepend({1,2,3}, 4)        -- {4,1,2,3}

        append({1,2,3}, {5,5,5})   -- {1,2,3,{5,5,5}}
        prepend({}, 9)             -- {9}
        append({}, 9)              -- {9}

La longitud de la nueva secuencia es siempre mayor en una unidad (1) que la longitud de la secuencia original. El ítem a agregarse a la secuencia puede ser un átomo u otra secuencia.

Estas dos funciones internas, append() y prepend(), tienen alguna similaridad con el operador de concatenación &, pero hay claras diferencias, por ejemplo:

        -- agregando una secuencia es diferente
        append({1,2,3}, {5,5,5})   -- {1,2,3,{5,5,5}}
        {1,2,3} & {5,5,5}          -- {1,2,3,5,5,5}

        -- agregando un átomo es lo mismo
        append({1,2,3}, 5)         -- {1,2,3,5}
        {1,2,3} & 5                -- {1,2,3,5}

2.2.10 Cuadro de precedencias

La precedencia de operadores en expresiones es la siguiente:

        precedencia más alta:   función/tipo llamadas

                                unario-  unario+  not

                                *  /

                                +  -

                                &

                                <  >  <=  >=  =  !=

                                and  or  xor

        precedencia más baja:   { , , , }

Así, 2+6*3 significa 2+(6*3) en lugar de (2+6)*3. Los operadores en la misma línea de arriba que tienen igual precedencia se los evalúa de izquierda derecha.

El símbolo igual '=' usado en una sentencia de asignación no es un operador, sino parte de la sintaxis del lenguaje.



2.3 Euphoria vs. lenguajes convencionales

Al basar a Euphoria en una estructura de datos recursiva, general y simple, se evitó la tremenda cantidad de complejidad que se encuentra normalmente en otros lenguajes de programación. Los arrays, estructuras, uniones, arrays de registros, arrays multidimensionales, etc, de otros lenguajes se pueden representar muy fácilmente en Euphoria con secuencias. También, estructuras de mayor nivel como listas, pilas, colas, árboles ,etc.

Además, en Euphoria se pueden tener secuencias de tipos variados; puede asignar cualquier objeto a un elemento de una secuencia; y las secuencias crecen y decrecen fácilmente en longitud, sin tener que preocuparse del asunto de la asignación de memoria. No se tiene que declarar de antemano la disposición exacta de la estructura de datos, pudiendo cambiar dinámicamente cuando se necesite. Es sencillo escribir código genérico, donde por ejemplo, puede poner y quitar en una pila distintos tipos de objetos de datos. Hacer una lista flexible que pueda contener una variedad de distintos tipos de objetos de datos, una cuestión trivial en Euphoria, pero requiere de una buena cantidad de pesado código en otros lenguajes.

Las manipulaciones de estructuras de datos son muy eficientes, ya que el intérprete Euphoria apuntará a objetos de datos grandes, en lugar de copiarlos.

La programación en Euphoria está totalmente basada en la creación y manejo flexible de secuencias dinámicas de datos. Las secuencias son todo - no hay otra estructura de datos que aprender. Se opera en un mundo sencillo, seguro y elástico de valores, que está lejos del tedioso y peligroso mundo de los bits, bytes, punteros y "colgadas" de las máquinas.

A diferencia de otros lenguajes como LISP y Smalltalk en Euphoria, la "recolección de desperdicios" de almacenamiento sin uso, es un proceso continuo que nunca causa demoras aleatorias en la ejecución de un programa y no preasigna enorme regiones de memoria.

Las definiciones de los lenguajes convencionales como C, C++, Ada, etc. son muy complejas. La mayoría de los programadores terminan usando sólo un subconjunto del lenguaje. Los estándares ANSI de esos lenguajes se perecen a complicados documentos legales.

Se verá forzado a escribir código distinto para cada tipo de dato diferente, simplemente para copiar el dato, pedir su longitud actual, concatenarlo, compararlo, etc. Los manuales de esos lenguajes contienen rutinas como "strcpy", "strncpy", "memcpy", "strcat", "strlen", "strcmp", "memcmp", etc., donde cada una solamente trabaja con alguno de todos los tipos de datos.

La mayoría de la complejidad gira en torno de los tipos de datos. ¿Cómo define nuevos tipos? ¿Qué tipos de datos se pueden mezclar? ¿Cómo convierte un tipo en otro, en una forma tal que mantenga feliz al compilador? Cuando necesita hacer algo que requiere flexibilidad en tiempo de ejecución, frecuentemente se encuentra tratando de engañar al compilador.

En esos lenguajes, el valor numérico 4 (por ejemplo) puede tener diferentes significados dependiendo si se trata de un entero, un caracter, un byte, un doble, etc. En Euphoria, 4 es el átomo 4, y punto. En algunas oportunidades, Euphoria los llama tipos como veremos más tarde, pero el concepto es mucho más simple.

Las cuestiones de la asignación y desasignación dinámica de memoria, consumen una gran parte del tiempo de codificación y depuración de los programadores en estos lenguajes, y hacen que los programas sean más difíciles de comprender. Los programas que tienen que correr continuamente, frecuentemente muestran "agujeros" de memoria, ya que toma un trabajo muy disciplinado la liberación segura y correcta de todos los bloques de memoria una vez que ya no se los necesita más.

Las variables de puntero se usan extensivamente. Se ha llamado al puntero el "ir a" de las estructuras de datos. Esto fuerza a los programadores a pensar en los datos como si estuvieran ligados a posiciones fijas de memoria, donde se los tiene que manejar de formas intrincadas, no portables y a bajo nivel. Euphoria no tiene punteros, ni los necesita.



2.4 Declaraciones


2.4.1 Identificadores

Los identificadores, que constan de nombres de variables y otros símbolos definidos por el usuario, pueden tener cualquier longitud. Las mayúsculas y las minúsculas son distintas. Los identificadores tienen que comenzar con una letra, a su vez seguida por otras letras, dígitos o caracteres de subrayado. Las siguientes palabras reservadas tienen un significado especial en Euphoria, por lo que no se las puede usar como identificadores:

    and            end             include          to
    by             exit            not              type
    constant       for             or               while
    do             function        procedure        with
    else           global          return           without
    elsif          if              then             xor

El editor Euphoria muestra estas palabras en azul. Se pueden usar los identificadores para nombrar:

  • procedimientos
  • funciones
  • tipos
  • variables
  • constantes


Procedimientos

Realizan algún cálculo y pueden tener una lista de parámetros, por ejemplo:

        procedure vacio()
        end procedure

        procedure dibujar(integer x, integer y)
            position(x, y)
            puts(1, '*')
        end procedure

Hay una cantidad fija de parámetros con nombre, pero esto no es restrictivo, ya que cualquier parámetro puede ser una secuencia de longitud variable de objetos arbitrarios. En muchos lenguajes, no es posible tener las listas de parámetros de longitud variable. En C, tiene que utilizar unos extraños mecanismos que son lo suficientemente complejos como para que el programador medio tenga que consultar el manual o al gurú local.

Se pasa una copia del valor de cada argumento. Las variables de parámetros formales se pueden modificar dentro del procedimiento, pero no afecta al valor de los argumentos.

Nota de rendimiento:
El intérprete no copia secuencias o números de punto flotante, a menos que sea necesario. Por ejemplo:
            y = {1,2,3,4,5,6,7,8.5,"ABC"}
            x = y
La sentencia x = y no causará que se cree una nueva copia de 'y'. Tanto 'x' como 'y' simplemente "apuntarán" a la misma secuencia. Si luego hacemos x[3] = 9, entonces se creará una secuencia separada para 'x' en la memoria (aunque aún solo será una copia compartida de 8.5 y "ABC"). Lo mismo se aplica a las "copias" de argumentos pasados a las subrutinas.

Funciones

Son como los procedimientos, pero devuelven un valor, y se las puede usar en expresiones, por ejemplo:

        function max(atom a, atom b)
            if a >= b then
                return a
            else
                return b
            end if
        end function

Se puede devolver cualquier objeto Euphoria. Puede, de hecho, tener múltiples valores de retorno al devolver una secuencia de objetos. Por ejemplo:

        return {x_pos, y_pos}

Usaremos el término general "subrutina" o simplemente "rutina" cuando una observación sea aplicable tanto a procedimientos como a funciones.


Tipos

Estos son funciones especiales que se usan para declarar los valores permitidos para las variables. Un tipo tiene que tener exactamente un parámtro y debería devolver un átomo que es tanto verdadero (no-cero) como falso (cero). También se puede llamar a los tipos como cualquier otra función. Ver 2.4.3 Especificando el tipo de una variable.


Variables

Se les puede asignar valores durante la ejecución, por ejemplo:

        -- x solo puede recibir valores enteros
        integer x
        x = 25

        -- a, b y c pueden recibir *cualquier* valor
        object a, b, c
        a = {}
        b = a
        c = 0

Al declarar una variable, le pone un nombre (que impide cometer errores ortográficos luego) y le especifica los valores que legalmente se le pueden asignar durante la ejecución del programa.


Constantes

Son variables que se asignan con un valor inicial que nunca puede cambiar, por ejemplo:

        constant MAX = 100
        constant mayor = MAX - 10, menor = 5
        constant nombres = {"Francisco", "Gloria", "Luis"}

El resultado de cualquier expresión se puede asignar a una constante, aún uno que involucre llamadas a funciones previamente definidas, pero una vez que se hace la asignación, el valor de la variable constante queda "bloqueado".

No se pueden declarar constantes dentro de una subrutina.


2.4.2 Ambito

El ámbito de un símbolo es la parte del programa donde la declaración de ese símbolo tiene efecto, es decir, donde el símbolo es visible.

En Euphoria, cada símbolo se tiene que declarar antes que se lo use. Puede leer un programa Euphoria desde el comienzo hasta el final sin encontrar ninguna variable o rutina que no se la haya definido. Es posible llamar a una rutina esté más adelante en el código fuente, pero tiene que usar las funciones especiales, routine_id(), y call_func() o call_proc() para hacerlo. Ver Parte II - Rutinas de Librería - Llamadas dinámicas.

Se puede llamar recursivamente a procedimientos, funciones y tipos. La recursión mútua, donde una rutina A llama a la rutina B, la cual directa o indirectamente llama a la rutina A, requiere del mecanismo routine_id().

Un símbolo está definido desde el lugar en donde se lo declara hasta el final de su ámbito. El ámbito de una variable declarada dentro de un procedimiento o función (una variable privada) termina la final del procedimiento o función. El ámbito de las demás variables, constantes, procedimientos, funciones y tipos termina al final del código fuente en el que están declarados y se los referencia como local, a menos que la palabra clave global preceda su declaración, por lo que su ámbito se extiende indefinidamente.

Cuando incluye (include) un archivo Euphoria en el archivo principal (ver 2.6 Sentencias especiales de alto nivel), solamente las variables y rutinas declaradas usando la palabra clave global serán visibles en el archivo principal. Las otras declaraciones, no globales, del archivo de inclusión no estarán visibles, y obtendrá un mensaje de error, "no declarada", si intenta usarlas en el archivo principal.

Los símbolos marcados como global se pueden usar externamente. Los deás símbolos solamente se pueden usar internamente dentro de sus archivos. Esta información es útil cuando se mantiene o mejora un archivo, o cuando se está aprendiendo a usarlo. Puede efectuar cambios en las rutinas y variables internas, sin tener que revisar otros archivos, ni tener que avisarles a los usuarios del archivo de inclusión.

A veces, al usar archivos de inclusión desarrollados por terceros, encontrará conflictos de nombres. El autor de un archivo de inclusión usó el mismo nombre para un símbolo global, que otro autor para otro símbolo. Si tiene el código fuente, simplemente puede editar uno de los archivos de inclusión para corregir el problema, pero tendría que repetir este proceso toda vez que se lance una nueva versión del archivo de inclusión. Euphoria tiene una manera sencilla de resolver este problema. Usando una extensión en la sentencia include, por ejemplo:

     include archivo_de_juan.e as juan
     include archivo_de_pepe.e as pepe
     juan:x += 1
     pepe:x += 2
En este caso, la variable 'x' está declarada en dos archivos diferentes, y necesita referirse a ambas en su archivo. Usando el identificador de espacio de nombres de tanto 'juan' como de 'pepe', puede agregar un prefijo a 'x', de forma de poder indicar a cual variable 'x' se está refiriendo. A veces decimos que 'juan' se refiere a un espacio de nombres, mientras que 'pepe' se refiere a otro espacio de nombres distinto. Puede adjuntar un identificador de espacio de nombres a cualquier variable definida por el usuario, constante, procedimiento o función. Puede hacer esto para resolver un conflicto, o simplemente para hacer las cosas más claras. Un identificador de espacio de nombres tiene ámbito local. Es conocido solamente dentro del archivo que lo declara, es decir, el archivo que contiene la sentencia include. Distintos archivos tienen que definir diferentes identificadores de espacios de nombres al referirse al mismo archivo de inclusión.

Euphoria lo alienta a restringir el ámbito de los símbolos. Si todos los símbolos fueran globales automáticamente para el programa entero, podría tener muchos conflictos de nombres, especialmente en programas extensos formados por archivos escritos por varios programadores. Un conflicto de nombres puede provocar un mensaje de error del compilador, o generar un "bug" demasiado sutil, donde distintas partes del programa modifiquen accidentalmente la misma variable sin estar enteradas de ello. Intente usar el ámbito más restrictivo que pueda. Haga las variables private en una rutina toda vez que sea posible, y si no lo es entonces hágalas local, en lugar de global.

Cuando Euphoria busca la declaración de un símbolo, primero verifica la rutina actual, luego el archivo actual y finalmente los globales en otros archivos. Los símbolos que son más locales sobreescribirán a los símbolos que son más globales. Al final del ámbito de los símbolos locales, el más global estará visible nuevamente.

Las declaraciones de constantes tienen que estar fuera de cualquier rutina. Las constantes pueden ser globales o locales, pero no privadas (private).

Las declaraciones de variables dentro de una subrutina tienen que estar al comienzo, antes de las sentencias ejecutables de la subrutina.

Las declaraciones en el nivel más alto, fuera de cualquier subrutina, no tienen que estar anidadas dentro de ningún ciclo o sentencia if.

La variable de control usada en el ciclo for es especial. Se declara automáticamente al comienzo del ciclo, y su ámbito termina al final del ciclo for. Si el ciclo está dentro de una función o procedimiento, la variable del ciclo es una variable private y no puede tener el mismo nombre que otra variable private. Cuando el ciclo está en el nivel superior, fuera de cualquier función o procedimiento, la variable del ciclo es local y no puede tener el mismo nombre que otra variable local en ese archivo. Puede usar el mismo nombre en distintos ciclos for, mientras que los ciclos no estén anidados. Las variables de ciclo no se declaran como las otras variables. El rango de valores especificados en la sentencia for, define los valores legales de la variable del ciclo - especificar un tipo sería redundante y no está permitido.


2.4.3 Especificando el tipo de una variable

Anteriormente hemos visto algunos ejemplos de tipos de variables, pero ahora los vamos a definir más precisamente.

Las declaraciones de variables tienen un nombre de tipo, seguido por la lista de las variables que se están declarando. Por ejemplo:

        object a

        global integer x, y, z

        procedure calcular(sequence q, sequence r)

Los tipos: object, sequence, atom e integer son predefinidos. Las variables de tipo object pueden tomar cualquier valor. Aquellas declaradas con el tipo sequence tienen que ser siempre secuencias. Aquellas declaradas con el tipo atom tienen que ser siempre átomos. Aquellas declaradas con el tipo integer tienen que ser átomos con valores enteros desde -1073741824 a +1073741823 inclusive. Puede efectuar cálculos exactos con valores enteros grandes, hasta 15 dígitos decimales, pero declárelos como atom, en lugar de integer.

Nota:
En la lista de parámetros de una función o procedimiento como el calcular() de arriba, un nombre de tipo solamente puede estar seguido por un solo nombre de parámetro.

Nota de rendimiento:
Los cáculos usando variables declaradas como enteros serán normalmente más rápidos que los cálculos cuyas variables están definidas como átomos. Si su máquina tiene hardware de punto flotante, Euphoria lo usará para manejar los átomos que no se representan como enteros. Si su máquina no tiene If your machine hardware de punto flotante, Euphoria llamará rutinas de software de aritmética de punto flotante contenidas en ex.exe (o en Windows). Puede forzar a que ex.exe saltee cualquier hardware de punto flotante, al establecer una variable de entorno:
            SET NO87=1
Se usarán rutinas de software más lentas, pero esto podría ser una ventaja si está preocupado por el "bug" de punto flotante de algunos de los primeros procesadores Pentium.

Para aumentar los tipos predefinidos, puede crear tipos definidos por el usuario. Todo lo que tiene que hacer es definir una función de un único parámtro, pero declararla con type ... end type en lugar de function ... end function. Por ejemplo:

        type hora(integer x)
            return x >= 0 and x <= 23
        end type

        hour h1, h2

        h1 = 10      -- ok
        h2 = 25      -- error! el programa se aborta con un mensaje

Las variables 'h1' y 'h2' se pueden asignar solamente con valores enteros en el rango de 0 a 23 inclusive. Después de cada asignación a 'h1' o 'h2' el intérprete llamará a hora(), pasándole el nuevo valor. Primero se verificará el valor para ver si es un entero (debido a "integer x"). Si lo es, la sentencia return se ejecutará para probar el valor de 'x' (es decir, el nuevo valor de 'h1' o 'h2'). Si hora() devuelve verdadero, la ejecución continua normalmente. Si hora() devuelve falso, entonces el programa se aborta con un mensaje de disgnóstico.

Se puede usar a "hora" para declarar parámetros de subrutina:

        procedure set_time(hora h)

set_time() se puede llamar solamente con un valor razonable de 'h', sino el programa se abortará con un mensaje.

El tipo de una variable se verificará luego de cada asignación a la variable (excepto donde el compilador puede determinar que una verificación no es necesaria), terminando el programa si la función de tipo devuelve falso. Los tipos de parámetro de la subrutina se verifican cada vez que se llama a la subrutina. Esta verificación garantiza que una variable nunca puede tener un valor que no pertenezca al tipo de dicha variable.

A diferencia de otros lenguajes, el tipo de una variable no afecta ningún cálculo en ella. El único valor de la variable que importa es en una expresión. El tipo solo sirve como una verificación de error que evita la "corrupción" de la variable.

Los tipos definidos por el usuario pueden capturar errores lógicos inesperados en el programa. No están diseñados para capturar o corregir los errores de las entradas del usuario.

La verificación de tipos se puede desactivar o activar entre subrutinas usando las sentencias especiales with type_check o without type_check. Por defecto, está activada.

Nota para probadores de prestaciones:
Al comparar la velocidad de los programas Euphoria contra programas escritos en otros lenguajes, debería especificar without type_check al inicio del archivo. Esto le permite a Euphoria saltear las verificaciones de tipos en tiempo de ejecución, con lo que se ahorra algún tiempo de ejecución. All other checks are still performed, por ejemplo, verificación de índices, variables no inicializadas, etc. Aún cuando desactive la verificación de tipos, Euphoria se reserva el derecho de hacer algunas verificaciones en lugares estratégicos, ya que esto le permite ejecutar su programa más rápido en muchos casos. Por lo tanto, puede obtener una falla de verificación de tipos aún cuando la verificación de tipos esté desactivada. Ya sea que la verificación de tipos esté activada o desactivada, nunca obtendrá una excepción a nivel de máquina. Siempre obtendrá un mensaje significativo de parte de Euphoria cuando las cosas no estén bien. (Esto puede no ser el caso cuando opera directamente con la memoria, o llama rutinas escritas en C o código de máquina).

El método de Euphoria para definir tipos es más simple que otros que puede encontrar en otros lenguajes, Euphoria le da al programador la más amplia flexibilidad para definir los valores legales para un tipo de datos. Cualquier algoritmo puede usarse para incluir o excluir valores. Puede declarar una variable de tipo objeto que le permitirá tomar cualquier valor. Se pueden escribir rutinas para trabajar con tipos muy específicos, o tipos muy generales.

Para muchos programas, es una pequeña ventaja definir nuevos tipos, y podría desear to stick con los cuatro tipos predefinidos. A diferencia de otros lenguajes, el mecanismo de tipos de Euphoria es opcional. No lo necesita para crear un programa.

Sin embargo, para programas extensos, las definiciones de tipos estrictos puede ayudar en la depuración. Los errores lógicos se capturan cerca de su origen y no se permite que se propaguen en formas sutiles al resto del programa. Además, es más fácil razonar acerca del malfuncionamiento de una sección de código cuando está seguro que las variables involucradas siempre tienen un valor legal, sino el deseado.

Los tipos también proveen una significativa documentación acerca del programa, haciéndolo más fácil de comprender a futuro tanto por el programador como por terceros. Combinado con la verificación de índices, la verificación de variables no inicializadas, y otra verificación que siempre está presente, la verificación estricta de tipos en tiempo de ejecución, hacen la depuración mucho más fácil en Euphoria que en la mayoría de los lenguajes. Esto también incrementa la confiabilidad del prgrama final, ya que muchos bugs latentes que podrían haber sobrevivido a la fase de pruebas en otros lenguajes, serán capturados por Euphoria.

Anécdota 1:
Se descubrió una cantidad de bugs, al portar un extenso programa C a Euphoria. Aunque se creía que este programa C era completamente "correcto", encontramos una situación donde se leía una variable no inicializada; un lugar donde el número de elemento "-1" de un array se leía y escribía constantemente; y una situaciñon en la que algo se escrbía fuera de pantalla. Estos problemas se conviertieron en errores que no eran fácilmente visibles al observador casual, consecuentemente habían sobrevivido las pruebas del código C.

Anécdota 2:
El algoritmo Quick Sort presentado en la página 117 of Escribiendo Programas Eficientes de Jon Bentley tiene un error de índice! El algoritmo a veces leerá el elemento solo antes del comienzo del array a ordenar, a veces leerá el elemento solo después del final del array. El algoritmo funcionará cualquier cosa que sea leida - esto es probable porque el bug no fue capturado. ¿Pero que pasa si no hay memoria (virtual) justo antes o después del array? Más tarde Bentley modifica el algoritmo para eliminar el bug -- pero presentó esta versión como siendo la correcta. ¡Aún los expertos necesitan la verificación de índices!

Nota de rendimiento:
Cuando se usan extensivamente los tipos definidos por el usuario, la veridicación de tipos agrega de un 20% a un 40% de tiempo de ejecución. Déjelo así, a menos que necesite realmente la velocidad adicional. También debería considerar desactivarlo solamente para unas pocas rutinas de ejecución pesada. El análisis de perfiles de ejecución puede ayudar con esta decisión.


2.5 Sentencias

Están disponibles los siguientes tipos de sentencias ejecutables:

En Euphoria, no se usan los puntos y comas, pero puede poner varias sentencias como guste en una línea, o una sentencia simple en varias líneas. No puede separar una sentencia a la mitad de un identificador, cadena, número o palabra clave.


2.5.1 Sentencia de asignación

Una sentencia de asignación asigna el valor de una expresión a una variable simple, o a un subrango, o a un elemento de una variable, por ejemplo:

        x = a + b

        y[i] = y[i] + 1

        y[i..j] = {1, 2, 3}

El valor previo de la variable, o elemento(s) de una variable con índices o subrangos, se descarta. Por ejemplo, suponga que 'x' era una secuencia de 1000 elementos que estaba inicializada con:

        object x

        x = repeat(0, 1000)  -- una secuencia de 1000 ceros
y luego le asignamos un átomo a 'x':
        x = 7
Esto es perfectamente legal porque 'x' está declarada como object. El valor previo de 'x', la sencuencia de 1000 elementos, sencillamente desaparecerá. Realmente, el espacio consumido por la secuencia de 1000 elementos se recicla automáticamente, debido a la asignación dinámica dememoria de Euphoria.

Observe que el símbolo igual '=' se usa tanto para la asignación como para probar la igualdad. Nunca hay confusión porque una asignación en Euphoria es una sentencia solamente, no se puede usar como una expresión (como en C).


Asignación con operador

Euphoria provee también algunas formas adicionales para la sentencia de asignación.

Para ahorrar escritura y hacer el código un poco más claro, puede combinar la asignación con uno de estos operadores:
      +   -   /   *   &

Por ejemplo, en lugar de decir:

      mylongvarname = mylongvarname + 1
Puede decir:
      mylongvarname += 1
En lugar de decir:
      galaxy[q_row][q_col][q_size] = galaxy[q_row][q_col][q_size] * 10
Puede decir:
      galaxy[q_row][q_col][q_size] *= 10
y en lugar de decir:
      accounts[start..finesh] = accounts[start..finesh] / 10
Puede decir:
      accounts[start..finesh] /= 10

En general, toda vez que tenga una sentencia de la forma:

      lado-izquierdo = lado-izquierdo op expresión
Puede decir:
      lado-izquierdo op= expresión
donde op es uno de estas:    +   -   *   /   &

Cuando el lado-izquierdo contiene índices o subrangos múltiples, la forma op= normalmente se ejecutará más rápido que la forma más larga. Cuando lo use, puede encontrar que la forma op= es ligeramente más legible que la forma larga, ya que la expresión del lado izquierdo aparece una sola vez.


2.5.2 Llamada a procedimientos

Una llamada a procedimiento inicia la ejecución de un procedimiento, pasándole una lista opcional de argumentos, por ejemplo:

        plot(x, 23)

2.5.3 La sentencia if

Una sentencia if prueba una condición para ver si es 0 (falso) o no-cero (verdadero) y entonces ejecuta la serie de sentencias que corresponde. Pueden haber cláusulas elsif y else opcionales. Por ejemplo:

        if a < b then
            x = 1
        end if


        if a = 9 and find(0, s) then
            x = 4
            y = 5
        else
            z = 8
        end if


        if char = 'a' then
            x = 1
        elsif char = 'b' or char = 'B' then
            x = 2
        elsif char = 'c' then
            x = 3
        else
            x = -1
        end if

Observe que elsif es la contracción de else if, pero es más limpia porque no requiere un end if que la acompañe. Hay un solo end if para toda la sentencia if, aún cuando hayan varios elsif's en ella.

Las condiciones if y elsif se prueban usando la evaluación de corto-circuito.


2.5.4 La sentencia while

Una sentencia while prueba una condición para ver si es no-cero (verdadero), y mientras sea verdadero el ciclo se ejecuta, por ejemplo:

        while x > 0 do
            a = a * 2
            x = x - 1
        end while

Evaluación de corto-circuito

Cuando la condición probada por if, elsif, o while contiene los operadores and u or, se utilizará la evaluación de corto-circuito. Por ejemplo:

        if a < 0 and b > 0 then ...
Si a < 0 es falso, entonces Euphoria no se molestará en probar si b es mayor que 0. Asumirá que todo el resto es falso. Similarmente,
        if a < 0 or b > 0 then ...
Si a < 0 es verdadero, entonces Euphoria decidirá inmediatamente que el resultado es verdadero, sin probar el valor de b.

En general, toda vez que tengamos una condición de la forma:

        A and B
donde A y B pueden ser dos expresiones cualesquiera, Euphoria tomará un atajo cuando A es falso e inmediatamente dará como resultado falso, sin mirar la expresión B.

Análogamente, con:

        A or B
cuando A es verdadero, Euphoria saltará la evaluación de la expresión B, y declarará al resultado como verdadero.

Si la expresión B contiene una llamada a una función y esa función tiene posibles efectos colaterales, es decir, que debería hacer más solamente devolver un valor, obtendrá una advertencia en tiempo de compilación. Las versiones más antiguas de Euphoria (anteriores a 2.1), no usaban la evaluación de corto-circuito, siendo pisible que algún código antiguo no funcione correctamente, aunque una búsqueda en los archivos de Euphoria concluyó que no hay ningún programa que dependa de efectos colaterales de esta manera.

La expresión B, podría contener algo que normalmente causaría un error en tiempo de ejecución. Si Euphoria salta la evaluación de B, no se descubrirá el error. Por ejemplo:

        if x != 0 and 1/x > 10 then  -- se evita el error por división por cero

        while 1 or {1,2,3,4,5} do    -- se evita la secuencia ilegal
B podria tener variables no inicializadas, índices fuera de rango, etc.

Esto podría verse como una codificación sloppy, pero de hecho frecuentemente le permite escribir algo en una forma más simple y más legible. Por ejemplo:

        if átomo(x) or length(x)=1 then
Sin la evaluación de corto-circuito, tendría un problema cuando 'x' fuese un átomo, ya que length() no está definida para átomos. Con la evaluación de corto-circuito, se verificará length(x) solamente cuando 'x' sea una secuencia. De forma similar:
        -- busca 'a' o 'A' en s
        i = 1
        while i <= length(s) and s[i] != 'a' and s[i] != 'A' do
             i += 1
        end while
En este ciclo, la variable 'i' podría ser eventualmente mayor que length(s). Sin la evaluación de corto-circuito, ocurriría un error de índice fuera de rango cuando s[i] se evalúa en la iteración final. Con la evaluación de corto-circuito, el ciclo terminará inmediatamente cuando i <= length(s) sea falso. Euphoria no evaluará s[i] != 'a' ni tampoco s[i] != 'A'. No ocurrirá un error de índice.

La evaluación de corto-circuito de and y or se realiza para las condiciones de if, elsif y while solamente. No se usa en otros contextos. Por ejemplo, la sentencia de asignación:

        x = 1 or {1,2,3,4,5}  -- x debería establecerse a {1,1,1,1,1}
Si aquí se hubiera usado la evaluación de corto-circuito, estableceríamos 'x' a 1, y no se tendría en cuenta a {1,2,3,4,5}, lo cual está mal. La evaluación de corto-circuito se puede usar en condiciones if/elsif/while, debido a que solamente nos importa si el resultado es verdadero o falso, el cual es un átomo.


2.5.5 La sentencia for

Una sentencia for constituye un ciclo especial con una variable de ciclo controlando que corra desde un valor inicial hasta un valor final (en forma ascendente o descendente), por ejemplo:

        for i = 1 to 10 do
            ? i   -- ? es una forma abreviada de print()
        end for

        -- tambien se permiten números fraccionarios
        for i = 10.0 to 20.5 by 0.3 do
            for j = 20 to 10 by -2 do    -- cuenta regresiva
                ? {i, j}
            end for
        end for

La variable de ciclo se declara automáticamente y existe hasta el final del ciclo. Fuera del ciclo, la variable no tiene valor, ni tampoco está declarada. Si necesita su valor final, cópiela en otra variable antes de abandonar el ciclo. El compilador no permitirá ninguna asignación a la variable del ciclo. Tanto el valor inicial, como el tope de ciclado y el incremento tienen que ser todos átomos. Si no se especifica el incremento, entonces se lo asume como 1. Los valores de tope e incremento se establecen cuando se entra en el ciclo, y no los afecta nada que ocurra durante la ejecución del ciclo. Ver también el ámbito de la variable de ciclo, en 2.4.2 Ambito.


2.5.6 La sentencia return

Una sentencia return regresa inmediatamente desde una subrutina. Si la subrutina es una función o tipo, entonces se tiene que devolver un valor, por ejemplo:

        return

        return {50, "ALFREDO", {}}

2.5.7 La sentencia exit

Una sentencia exit puede aparecer dentro de un ciclo while o un ciclo for. Provoca la terminación inmediata del ciclo, pasando el control a la primera sentencia que está después del ciclo, por ejemplo:

        for i = 1 to 100 do
            if a[i] = x then
                location = i
                exit
            end if
        end for

Es absolutamente común ver algo como esto:

        constant TRUE = 1

        while TRUE do
            ...
            if some_condition then
                exit
            end if
            ...
        end while
es decir, un ciclo while "infinito" que termina mediante una sentencia exit en algún punto arbitrario del cuerpo del ciclo.
Nota de rendimiento:
Euphoria optimiza este tipo de ciclos. En tiempo de ejecución, no se realiza ninguna prueba en lo más alto del ciclo. Es solamente un simple salto incondicional desde end while hacia la primera sentencia dentro del ciclo.

Con ex.exe, si se le ocurre crear un verdadero ciclo inifinito, sin que ocurra ninguna entrada /salida, no habrá forma de detenerlo. Tendría que presionar Ctrl+Alt+Supr, tanto sea para reiniciar la máquina, como (bajo Windows) terminar la sesión DOS. Si el programa tiene archivos abiertos para escritura, sería aconsejable ejecutar scandesk para verificar la integridad del sistema. Solamente cuando el programa una entrada por teclado, Ctrl+C abortará el programa (a menos que se use allow_break(0)).

Con exw.exe o exu, Ctrl+C siempre detendrá el programa inmediatamente.



2.6 Sentencias especiales de alto nivel

Euphoria procesa el archivo .ex en un solo paso, comenzando en la primera línea y siguiendo hasta la última. Cuando se encuentra la definición de un procedimiento o función, se verifica la sintaxis de la rutina y se la convierte en una forma interna, pero no ocurre la ejecución. Cuando se encuentra una sentencia que está fuera de cualquier rutina, se verifica la sintaxis de la rutina y se la convierte en una forma interna y se la ejecuta inmediatamente. Una práctica común es inicializar inmediatamente una variable global, solo después de su declaración. Si su archivo .ex contiene solamente definiciones de rutinas, pero no sentencias de ejecución inmediata, nada ocurrirá cuando intente correrlo (salvo la verificación de sintaxis). Necesita tener una sentencia inmediata para llamar a su rutina principal (ver 1.1 Programa de ejemplo). Es absolutamente posible tener un archivo .ex con sentencias inmediatas únicamente, por ejemplo podría querer usar a Euphoria como una simple calculadora, escribiendo solamente la sentencia print() (o ?) dentro de un archivo y entonces ejecutarlo.

Como hemos visto, se puede usar cualquier sentencia Euphoria, incluyendo ciclos for, ciclos while, sentencias if, etc. (pero no return), en el nivel superior, es decir, fuera de cualquier función o procedimiento. En suma, las siguientes sentencias especiales solo pueden aparecer en el nivel superior:

  • include
  • with / without


2.6.1 Include

Cuando se escribe un programa extenso, frecuentemente es provechoso dividirlo en archivos separados lógicamente, usando sentencias include. A veces querrá reutilizar algún código que haya escrito previamente, o aquel que no haya escrito. En lugar de copiar este código en su programa principal, puede usar una sentencia include para referirse al archivo que contiene el código. La primera forma de la sentencia include es:

include nombre de archivo
Lee (compila) un archivo fuente de Euphoria.

Por ejemplo:

                   include graphics.e

Cualquier código de alto nivel que incluya el archivo de inclusión, será ejecutado.

Cualquier símbolo global que ya esté definido en el archivo principal estará visible en el archivo de inclusión.

N.B. Solamente aquellos símbolos definidos como global en el archivo de inclusión estarán visibles (accesibles) en el resto del programa.

Si se da un nombre de archivo absoluto, Euphoria lo usará. Cuando se da un nombre de archivo relativo, Euphoria buscará primero en el mismo directorio en el que está dado el archivo principal en la línea de comandos de ex (o exw o exu). Si no se lo ubica, y se definió la variable de entorno EUINC, se lo buscará en cada directorio listado en EUINC (de izquierda a derecha). Finalmente, si todavía no se lo encuentra, se lo buscará en euphoria\include. Este directorio contiene los archivos de inclusión estándares de Euphoria. La variable de entorno EUDIR le dice a ex.exe/exw.exe/exu donde encontrar el directorio euphoria. EUINC debería ser una lista de directorios, separados por puntos y comas (comas en Linux), similarmente a la variable PATH. Puede agregarse al archivo AUTOEXEC.BAT, por ejemplo:
SET EUINC=C:\EU\MYFILES;C:\EU\WIN32LIB
Esto le permite organizar sus archivos de inclusión de acuerdo a las áreas de aplicación, y evitando agregar numerosos archivos no relacionados al directorio euphoria\include.

Un archivo de inclusión puede incluir otros archivos. De hecho, puede "anidar" archivos de inclusión hasta 10 niveles de profundidad.

Los nombres de los archivos de inclusión típicamente terminan en .e, o a veces en .ew o .eu (cuando se los escribe para usarlos con Windows o Linux). Es solamente una convención no exigible.

Si el nombre del archivo (o su ruta) contiene espacios en blanco, tiene que encerrarlo entre comillas dobles, en caso contrario las comillas son opcionales. Por ejemplo:

                   include "c:\program files\myfile.e"

Con excepción posiblemente de definir un nuevo identificador de espacio de nombres (ver debajo), una sentencia include será ignorada si un archivo con el mismo nombre ya se ha incluido.

Una sentencia include tiene que escribirse en una línea propia. Solamente puede aparecer un comentario después en la misma línea.

La segunda forma de la sentencia include es:
include nombre de archivo as identificador_de_espacio_de nombres

Esto es como un incluse simple, pero también define un identificador de espacio de nombres que se puede anexar a símbolos globales en un archivo de inclusión al que quiera referirlo en el archivo principal. Esto podría ser necesario para evitar referencias ambigüas para esos símbolos, o para hacer el código más legible. Ver reglas de ámbito.


2.6.2 With / without

Estas sentencias especiales afectan la manera en que Euphoria traduce su programa en la forma interna. No cambian la lógica de su programa, pero pueden afectar la información de diagnóstico que obtiene al correr el programa. Leer más información en 3. Debugging and Profiling.

with
Activa una de las opciones: profile, profile_time, trace, warning o type_check . Las opciones warning y type_check están inicialmente activadas, mientras que profile, profile_time y trace no.

Cualquier advertencia que se emita, aparecerá en pantalla después que el programa haya terminado de ejecutarse. Las advertencias indican problemas menores. Una advertencia nunca detendrá la ejecución del programa.

without

Desactiva una de las opciones mencionadas.

También hay una opción especial with, pocas veces utilizada, donde un número de código aparece después de with. En versiones anteriores, este código se usaba por RDS para hacer un archivo inmune al agregado de la sentencia count en la Edición de Dominio Público.

Puede seleccionar cualquier combinación de configuraciones, pudiendo cambiarlas, pero los cambios tienen que ocurrir entre subrutinas, no dentro de una subrutina. La única excepción es que solamente puede activar un tipo de análisis de perfiles de ejecución para una dada corrida de su programa.

Un archivo de inclusión hereda la configuración de with/without en el punto en donde es incluido. Un archivo de inclusión puede cambiar esas configuraciones, pero volverán a su estado original al final del archivo de inclusión. Por ejemplo, un archivo de inclusión puede desactivar advertencias por sí mismo y (inicialmente) para cualquier archivo que lo incluya, pero no desactivará las advertencias en el archivo principal.

 

... continua en 3. Depuración y análisis de perfiles de ejecución