Programación mediante Prowl

(Prowl y su documentación fueron desarrollados por Sara Rodríguez Dorrego)
Índice

Introducción

Organización de un objeto

La organización de un programa en Prowl se reduce a objetos: cualquier elemento debe estar dentro de uno. Los objetos están conpuestos única y exclusivamente de métodos y atributos.
Se propone un ejemplo simple a continuación.

object Punto
  attribute - x = 0
  attribute - y = 0
  method + mueve(a, b)
  {
    x = a
    y = b
    return;
  } 

  method + toString()
  {
    refrence toret = "";

    toret = toret.concat( x.toString() );
    toret = toret.concat( ", " );
    toret = toret.concat( y.toString() );

    return toret;
  } 

  method + doIt()
  {
    this.mueve( 100, 100 );
    System.console.write( this );
  }
endObject

Cómo compilar un objeto Prowl

Los objetos deberían ser guardados en archivos de texto con extensión .pwl. Una vez guardados, deben ser compilados con Prowl, generando un archivo ensamblador. Este archivo ensamblador debe ser compilado con za. Véase un ejemplo con el objeto anterior.

$ ./prowl Punto.pwl Punto
$ ./za Punto
$ ./zvm Punto
100, 100

Comentarios

En Prowl los comentarios empiezan por los caracteres /* y terminan con los caracteres */. Pueden comprender varias líneas y estar distribuidos de cualquier forma. Todo lo que esté entre /* (inicio del comentario) y */ (fin del comentario) es ignorado por el compilador.

/*  Esto es un comentario */

/*  Esto es un comentario
     que ocupa dos líneas */

/************************
*  Esto es un comentario un *
*  poco adornado                   *
*************************/


También hay otro tipo de comentario que sólo abarca una línea. Se ponen los caracteres // que marcan el comienzo del comentario y el fin de la línea, el final del comentario. Si se desea poner comentarios de varias líneas, hay que colocar la doble barra al comienzo de cada línea. Los ejemplos anteriores los podríamos escribir de la siguiente manera:

//  Esto es un comentario

//  Esto es un comentario
//   que ocupa dos líneas

//************************
//  Esto es un comentario un *
//  poco adornado            *
//************************

Identificadores y palabras reservadas

Un identificador es un conjunto de caracteres que sirve para nombrar un método, una referencia, un atributo,…  

Identificadores válidos en Prowl son aquellos cuyo primer carácter es una letra o un subrayado y los siguientes pueden ser letras, subrayados o números.

Identificadores inválidos: 8objeto, miObj$  
Identificadores válidos: _objeto, miObj8  

    Algo muy importante a recalcar es que en Prowl, se distinguen las mayúsculas de las minúsculas. De esta manera podemos conseguir un mayor número de identificadores, pero a partir de esto pueden surgir errores de atributos o métodos no encontrados.

    Las palabras reservadas son unos cuantos identificadores que el lenguaje ya tiene asignados para cometidos especiales y por tanto, no pueden ser utilizados.

    A parte de las palabras reservadas del lenguaje Prowl, hay otros identificadores perfectamente válidos para este lenguaje que no se pueden utilizar. Éstos identificadores son los correspondientes a los registros que maneja la máquina virtual zero: __acc, __exc, __rr, __gp1, __gp2, __gp3, __gp4.  Para poder acceder al objeto que está ejecutando el método que está activo, tenemos que acceder mediante this, no mediante __this.

Operadores reservados


    Hay muchos signos que forman operadores, pero otras combinaciones no forman ni operadores ni identificadores. En Prowl, se usan los siguientes caracteres y combinaciones de caracteres como operadores o para puntuación:

      

{ += -= *=
} /= != %=
( <= >= --
) ++ == &&
[ || < >
] = - +
. * / %
: , ; ! ^ /* */ //

Objetos

Los objetos de un programa en Prowl, se crean con las palabras reservadas object y endObject. Estas palabras indican el inicio y el fin de un objeto respectivamente. La palabra reservada object deberá ir acompañada al menos de un identificador que será el nombre del objeto:

object <nombre_objeto>

Debido que Prowl soporta la herencia, es posible que deseemos indicar que un objeto hereda de otro ya existente. La manera de indicarlo es colocar dos puntos (‘:’) después del nombre del objeto que se está definiendo y, a continuación el nombre del objeto del que se hereda.

object <nombre_objeto> : <nombre_objeto_padre>

Otra propiedad de Prowl es la herencia dinámica (posibilidad de cambiar al ‘padre’ de un objeto en tiempo de ejecución). La manera de poder indicar que un objeto soporta la herencia dinámica es:

object <nombre_objeto>: <nombre_objeto_padre1>  (condición) , <nombre_objeto_padre2>  (condición) , … , <nombre_objeto_padreN> (condición)

Primero se pone la palabra reservada object seguida del nombre del objeto y de los dos puntos, que indican que el objeto hereda de otro. A continuación se pone el nombre del objeto del que va a heredar seguido de la condición. Cuando esta condición se cumple, el objeto que se está definiendo cambia su atributo parent y apunta al objeto padre al que se refiere la condición. Como mínimo se tienen que poner dos objetos, ya que sino, no tendría ningún sentido. Se pueden indicar tantos objetos como se quieran, pero eso sí, todos estos objetos tienen que estar separados por comas.

Por defecto el padre de un objeto inicialmente, es el situado más a la izquierda, es decir, el que colocamos primero en la lista de los posibles padres que puede tener ese objeto.

Ejemplos de declaraciones de objetos:

object Persona
        attribute + nombre = "juan";
        attribute + salario = 18000;

        method + toString()
        {
                return nombre.concat( "\n" );
        }

        method + ponSalario(s)
        {
                salario = s;
                return;
        }
endObject

object Empleado : Persona
        method + toString()
        {
                return nombre.concat( " (empleado)\n" );
        }
endObject

object EmpleadoEmpresa : Empleado( salario < 20000 ),
                         Directivo( salario >= 20000)
        method + toString()
        {
                return super();
        }
endObject

En el interior de un objeto podemos declarar atributos (attribute) y métodos (method).  Además de la declaración de métodos y atributos, podemos insertar algunas propiedades acerca del objeto que estamos creando.

Declaración de atributos

Para definir los atributos de un método se utiliza la palabra reservada attribute. La declaración de un atributo siempre tiene que estar dentro de un objeto, es decir, siempre tiene que estar dentro del par object/endObject. La sintaxis adecuada de la declaración de un atributo es la siguiente:

attribute <+/-> <nombre_atributo> ;

Para definir un atributo, se pone la palabra reservada attribute seguido del símbolo ‘+’, si el atributo que estamos declarando es un atributo público o del símbolo ‘-’ si es privado. Por último, ponemos el nombre del atributo que estamos declarando seguido de un punto y coma (‘;’)  que indica el fin de una instrucción.

Es posible declarar e inicializar sólo un atributo a la vez:

attribute <+/-> <nombre_atributo> = <expresión>;

La sintaxis es exactamente igual que la anterior, sólo que esta vez se debe poner un símbolo igual (‘=’) seguido de la expresión con la que se quiere inicializar el atributo.

Ejemplos de declaraciones de atributos
attribute + nombre = "juan";
attribute + salario = 18000;
attribute + v;

Métodos

Declaración de métodos

Para definir métodos se usa la palabra reservada method. La declaración de un método siempre tiene que estar dentro de un objeto, es decir, siempre tiene que estar dentro del par object/endObject. La sintaxis adecuada de la declaración de un método es la siguiente:

method <+/-> <nombre_método> ( [<parámetro1>, [< parámetro2>, … , <parámetroN>]] ) { <cuerpo_método> }

Para definir un método, se pone la palabra reservada method seguido del símbolo ‘+’ o del símbolo ‘-’ (que indican que el método es público o privado respectivamente) y del nombre del método que se está declarando.

Al del nombre del método le siguen todos los parámetros formales de dicho método entre paréntesis en el caso de que el método que estamos declarando los tuviese. Si no tuviese ningún parámetro formal, se ponen los paréntesis vacíos. El cuerpo del método se encierra entre llaves las cuales indican inicio y fin del cuerpo del método.

El cuerpo de los métodos

En un programa implementado en Prowl, debe haber uno método principal, desde el cual se comenzará la ejecución del programa. Este método es el llamado doIt(). Prowl si no encuentra este método en el fichero que se le pasa por parámetro muestra el siguiente mensaje de error.

El cuerpo de un método siempre debe tener al final al menos un return. Es decir un ejemplo del método más simple que habría en Prowl sería el siguiente:

object  objetoSimple
       method + doIt()
       {
              return;
       }
endObject

El cuerpo de un método en Prowl se podría dividir en distintas “secciones” colocadas en un orden determinado dentro del método, como podemos observar a continuación:

Secciones de un método
precondiciones y postcondiciones
cuerpo del método
gestor de excepciones


Lo primero que se debe indicar en el cuerpo de un método son las precondiciones y poscondiciones que debe cumplir un método en el caso de éstas existan. Después de las precondiciones y poscondiciones se pondrían las instrucciones.

En el caso de que el método que estemos implementando pueda lanzar alguna excepción, estas se pondrían a continuación de todas las instrucciones. Por último, al final de un método siempre debe aparecer un return. Es obligatorio, sino se pone un return al final de todos los métodos en Prowl, se producirá un error en tiempo de compilación.

En Prowl como máximo, en el cuerpo de un método sólo puede haber dos return. En el caso de que haya más de dos instrucciones return, Prowl mostrará un mensaje de error.

Precondiciones y postcondiciones

Introducción a la programación por contrato

Cuando hablamos de programación por contrato estamos hablando de corrección del software. Por corrección entendemos, la capacidad del software para realizar con exactitud su tarea tal como definen sus especificaciones.
La corrección es la propiedad fundamental del software. Si nuestro software no cumple las especificaciones, poco importa su usabilidad, seguridad, etc… La robustez, capacidad de un sistema para reaccionar adecuadamente ante situaciones excepcionales, es un complemento a la corrección. Ante una entrada errónea, será más robusto el programa que emita un mensaje de error y termine limpiamente que otro que cause eventos catastróficos.
Si introducimos los conceptos de precondición y poscondición:
Precondiciones: Condiciones que definen el estado antes de la ejecución de un programa.
Postcondiciones: Condiciones que definen el estado después de la ejecución de un programa.
Podemos dar otra definición al término de corrección, podemos decir que un programa es correcto si al ejecutarse satisfaciendo sus precondiciones también satisface sus postcondiciones.
Si tenemos un algoritmo A, con la precondición P y la postcondición Q:

{P}A{Q}

Según este esquema, para que nuestro programa sea correcto A tiene que satisfacer Q a la entrada de P.

Cómo utilizar programación por contrato en Prowl

En Prowl, podemos especificar las precondiciones y postcondiciones que un método debe cumplir para su correcta ejecución. Un método puede tener precondiciones, postcondiciones o ninguna de las dos.

Las precondiciones y postcondiciones deben situarse al principio del método que se quiere implementar.

La sintaxis de las precondiciones es la siguiente:

requires{ <instrucción_assert1> [<instrucción_assert2>;…; <instrucción_assert3>] }

Se escribe la palabra reservada requires seguida de como mínimo una instrucción assert encerrada entre llaves, independientemente si sólo hay una instrucción assert o si hay varias.

La sintaxis de las postcondiciones es la siguiente:

enforce{ <instrucción_assert1> [<instrucción_assert2>; … ;<instrucción_assert3>] }

Se escribe la palabra reservada enforce seguida de como mínimo una instrucción assert encerrada entre llaves, independientemente si sólo hay una instrucción assert o si hay varias.

La instrucción assert sólo interrumpe la ejecución si no se cumple una condición especificada. Si la condición se cumple no hace nada.

La sintaxis de la instrucción assert es la siguiente:

assert ( <condicion> [, <cadena> ])

Se escribe la palabra reservada assert y entre paréntesis la condición que se debe cumplir para ejecutar el método. Si además de la condición tiene un segundo parámetro, una cadena de texto, si la condición no se cumple, se genera una excepción EAssert con dicho texto.

Un ejemplo de la utilización de la programación por contrato en Prowl, es el siguiente:

    method + dividir(a, b)
    {
        
        requires {
         assert( a isInstanceOf Int, "Dividendo no numérico." );
         assert( b isInstanceOf Int, "Divisor no numérico." );    
         assert( b != 0, "Divisor no puede ser 0" );
        }

        enforce {
          assert( toret isInstanceOf Int, "Resultado no numérico (?)"    );
        }

        reference toret;

        toret = a / b;
        return toret;

        onException( e ) {
            if ( e isInstanceOf EAssert ) {
                System.console.write( e.getErrorMessage() );
            }
            else System.console.write( "FATAL: Error interno" );
        }
    }

    Si los parámetros del método dividir, no son enteros o si es el cero, se genera una excepción con el texto correspondiente y se para la ejecución del método. En caso contrario, se ejecuta el cuerpo del método y si el resultado no es un entero se lanza una excepción con el texto correspondiente a dicha excepción y se para la ejecución del método.

Cuerpo del método

El cuerpo del método está formado por instrucciones, así como el cuerpo del bloque de excepciones, por lo que lo comentado aquí también es válido para él.

Una instrucción la podríamos definir como un comando básico de programación, aceptable tanto por un lenguaje como por la computadora misma. En Prowl, toda instrucción debe acabar en un punto y coma. Las instrucciones válidas en Prowl son las que se comentan en los siguientes apartados.

Referencias

Las referencias locales, se declaran de la siguiente manera:

reference <nombre_referencia>;

Se pone la palabra reservada reference seguida del nombre que le queramos dar a la referencia y del punto y coma que indica el fin de la instrucción.
    
Si queremos inicializar la referencia al mismo tiempo que la declaramos, tendremos que poner después del nombre de la referencia, el símbolo igual seguido de la expresión con la que deseemos inicializar la referencia.
    

reference <nombre_referencia> = <expresión>;

Ejemplos de declaraciones de atributos:

reference x = ( System.getCurrentTime().instantInDay ) % 100;
reference y;

Sentencia GOTO

La instrucción goto en Prowl tiene la misma función que la sentencia de salto no condicional goto de C, C++.  Goto realiza un salto dentro de un bloque de código. En el caso de C, C++ realiza un salto dentro de una función y en Prowl, realiza un salto dentro de un método. La sentencia goto funciona con etiquetas.

goto <nombre_etiqueta>;

La sentencia goto sustituye a las sentencias break y continue ya que estas sentencias no las soporta Prowl. Con goto podemos hacer lo mismo que utilizando (si Prowl las soportase) las sentencias break y continue, es decir, básicamente romper la ejecución de bucles y otras tareas menores

Etiquetas

Las etiquetas señalan un grupo de sentencias y consisten en dos puntos seguidos de un nombre de identificador válido (finalizando con punto y coma).

En un lenguaje de alto nivel, como Prowl, se mantiene este tipo de instrucción debido a que existe la sentencia de salto no condicional goto, pero no break y continue (explicado en el apartado anterior).

: <nombre_etiqueta>;

Sentencia throw

La sentencia throw lanza una excepción. Su sintaxis es muy sencilla, basta sólo con poner la palabra reservada throw seguida del nombre del objeto que se quiere lanzar.     

throw <nombre_objeto>

Además del nombre del objeto también se puede poner una cadena de caracteres que describa la excepción. La sintaxis sería la siguiente:

throw <nombre_objeto>’,’<cadena>

Mensajes

Un mensaje es la ejecución de un método de un objeto. Los mensajes a métodos se realizan básicamente de la misma forma que en el lenguaje Java:

<nombre_objeto>.<nombre_método>

    Es muy habitual que entre el nombre del objeto y el método se sitúen varios nombres de atributos o de métodos que devuelven una referencia a un objeto.

    Para mandar un mensaje a un método del padre de un objeto, se realiza de la siguiente manera:

super ( [<parámetro1>, [< parámetro2>, … , <parámetroN>]] )

    Se escribe la palabra reservada super seguida de los parámetros necesarios entre paréntesis.

    Con la palabra reservada this, mandamos un mensaje al objeto que está ejecutando dicho método.

Operadores de incremento y decremento

Estos operadores funcionan de manera similar, a los operadores de decremento e incremente de C.

En Prowl  no importa dónde ponemos el operador, es decir, si lo ponemos delante o detrás de la referencia o atributo al cual lo queremos sumar o restar uno (o lo que es lo mismo, si es un operador prefijo o postfijo). Prowl, independientemente de la colocación de estos operadores siempre va a llevar a cabo las siguientes acciones:

  1. Suma o resta una unidad a la referencia o atributo.
  2. Utiliza la referencia o atributo.

El resultado, por tanto, de poner x++ y ++x es exactamente el mismo.

Asignaciones

<identificador>=<expresión> [= <expresión2> = [<expresión3>=…=<expresiónN>]]

El operando de la izquierda del símbolo ‘=’ toma el valor del operando de la derecha.

Prowl admite tanto la asignación simple como la asignación múltiple. Es decir, en Prowl se puede asignar un valor tanto, a una referencia como a un atributo, como asignar un mismo valor a varias referencias o atributos en la misma instrucción.

      a = 10;
      area = calculaArea(radio);

Lo que no tendría sentido poner, serían expresiones como las siguientes:

      10 = a;
      calculaArea(radio) = area;

Dado que el receptor de una asignación siempre debe ser una referencia local, o un atributo perteneciente al objeto donde reside el método (o derivado).

Aunque el operador más importante y más frecuentemente usado es el operador asignación =, existen otros operadores que realizan una asignación sobre su operando. Estos operadores son los siguientes:

Operador Ejemplo Significado
+= a+=b Suma a e b y le asigna el resultado a x
-= a-=b A a le resta b y le asigna el resultado a x
*= a*=b Multiplica a por b y se lo asigna a x
/= a/=b Divide a entre b y se lo asigna a x
%= a%=b Haya el módulo de a y b y se lo asigna a x

En los operadores de asignación, también incluimos los operadores de decremento (‘--’) e incremento (‘++’), ya que éstos realizan implícitamente una asignación sobre su operando aunque no siguen la sintaxis típica de una asignación.

Propiedades

Dentro de un programa escrito en Prowl, cabe la posibilidad de indicar las propiedades relativas a los objetos y métodos existentes. La manera de indicarlo en Prowl es mediante las propiedades cuya sintaxis es la siguiente:

[ DOC | DEBUG | <obj>  [ = <cadena_con_las_propiedades_obj_met>] ]

Las propiedades, como se puede observar, van encerradas entre corchetes en cuyo interior debe ir DOC o DEBUG, o cualquier otro objeto que derive de Property. Éste último está definido en la librería estándar interna, y los dos únicos objetos derivados en esta librería son Doc y Debug. Usar las propiedades sin ninguna cadena que indique la propiedad o propiedades de ese método u objeto, no tiene mucho sentido. Por eso, aún así de manera opcional, es posible poner una cadena que exprese las propiedades de dicho objeto o método anteponiéndole el símbolo igual.

object op : ConsoleApplication
        [DOC= "Objeto que realiza sumas y restas"]

        method + sumar(a, b)
        {
            [DOC= "Método que suma dos números"]
            return a + b;
        }

        method + restar(a, b)
        {
            [DOC= "Método que resta dos números"]
            return a - b;
        }

    
        method + doIt()
        {
            [DOC="Punto de entrada. Pide dos números interactivamente y los muestra"]

            reference x;
            reference y;

            System.console.write ( "Numero: " ) ;
            x = Int.parseString ( System.console.read() );
            System.console.lf();

            System.console.write ( "Numero: " ) ;
            y = Int.parseString ( System.console.read() );
            System.console.lf();
                
            System.console.write( "\nEl resultado de la suma es " );
            System.console.write( Op_mat.sumar( x, y ) );
            System.console.lf();

            System.console.write( "\nEl resultado de la resta es " );
            System.console.write( Op_mat.restar( x, y ) );
            System.console.lf();

            return;
        }
endObject

Operaciones matemáticas

Las expresiones matemáticas son expresiones del tipo:

<id ó numero><op_aritmético><expresión> [<op_aritmético> <expresión2> <op_aritmético> [<expresión3><op_aritmético> … <op_aritmético> <expresiónN>]]

Prowl soporta cinco operadores aritméticos cuyo significado se muestra en la siguiente tabla:

Símbolo Operador Ejemplo
+ Suma 3 + 5
- Diferencia 3 - 5
* Producto 3 * 5
/ Cociente 10 / 5
% Módulo 5 % 3

El operador módulo da como resultado el resto de la división entera. Por ejemplo, el resultado de  20%7 es 6 que es el resto de la división entre 20 y 7.
La prioridad de evaluación de los operadores aritméticos es la que se muestra en la siguiente tabla, la cual está ordenada de mayor a menor prioridad:

Operadores
+ -
* / %

Estructuras de decisión: sentencia IF

La sentencia condicional IF, se usa para tomar decisiones. La sintaxis de la sentencia IF es la siguiente:

if( <condición> )
        <sentencia1>;
else
        <sentencia2>;

Los paréntesis de la condición son obligatorios y else sentencia2 se puede omitir. Si se cumple la condición se ejecutará la sentencia1, sino se ejecutará la sentencia2.
    Una sentencia puede ser simple o compuesta, en el caso que sea compuesta se deben poner entre llaves. Si la sentencia es simple pueden omitirse las llaves o no.

if( <condición> ) {
        <sentencia1>;
        ...
        <sentencian>;
}
else {
        <sentencia1>;
        ...
        <sentencian>;
}

Estructuras de repetición

do ... while

Si traducimos do-while al castellano significa «hacer [algo] mientras [se cumpla una condición]. El bucle do-while se ejecuta como mínimo una vez, ya que la condición se testea al final.

La sintaxis del bucle do-while es la siguiente:

do
       <sentencia>;
while( <condicion> );

La sentencia se ejecuta, luego se evalúa la condición. Si es cierta, se vuelve a ejecutar y así hasta que la expresión sea falsa.

Como en los caso de la sentencia condicional IF, si dentro del cuerpo del bucle do-while queremos ejecutar varias sentencias, éstas deben ponerse entre llaves:

do {
       <sentencia1>;
       ...
       <sentencian>;
} while( <condicion> );

Bucle while
El bucle while tiene un comportamiento inverso al del do-while. La sintaxis del bucle while es la siguiente:

while( <condicion> )
       <sentencia>;

En este caso, se evalúa primero la condición y luego se ejecuta la sentencia si la condición es cierta. Por lo tanto, si la condición es falsa la primera vez, no se ejecuta nunca la sentencia. El bucle se ejecuta siempre hasta que la condición sea falsa.

Como en los casos anteriores, si dentro del cuerpo del bucle while queremos ejecutar varias sentencias, éstas deben ponerse entre llaves:

while( <condicion> )
{
       <sentencia1>
       ...
       <sentencian>
}

Bucle for

El bucle For es muy similar al bucle While. De hecho:

for( <expr1>; <expr2>; <expr3>)
        <sentencia>

es equivalente a:

<expr1>;
while( <expr2> )
{
     <sentencia1>
     ...
     <sentencian>
     <expr3>;
}

El bucle For permite ejecutar una o varias sentencias un número repetido de veces. Su sintaxis es la siguiente:

for([inicialización];[condición];[iteración])
         sentencia;

El primer término inicialización, se usa para inicializar una variable índice, que controla el número de veces que se ejecutará el bucle. La condición representa la condición que ha de ser satisfecha para que el bucle continúe su ejecución. El incremento representa la cantidad que se incrementa la variable índice en cada repetición.

    Como podemos observar, todos los términos son optativos, se pueden poner o no. De esta manera, en Prowl podemos tener bucles for como los siguientes:

for(;<expr2>;) ;

for(;<expr2>;x++);

En Prowl podemos construir bucles for que no tengan condición de salida. Cada vez que Prowl se encuentra un bucle de este tipo (sin condición), muestra por pantalla un aviso, dando a entender su peligrosidad.

Prowl muestra un aviso en vez de un error, ya que aunque el bucle for no tenga condición de salida, éste puede ser que no sea infinito. Un ejemplo sería el siguiente bucle for:

object ej_for2 : ConsoleApplication
  method + doIt() {
      reference x = 0;
        
      for(;;x += 2) {
          if( x == 8) {
              goto salir;
          }

          System.console.write( x );
          System.console.lf();
      }

      :salir;   
      return;    
  }
endObject

Prowl muestra un aviso por pantalla, pero sin embargo el bucle for no es infinito, debido a que en el cuerpo del bucle se utiliza la sentencia goto para salir del bucle. La ejecución del ejemplo sería la siguiente:

0
2
4
6

En el caso de que el bucle construido sea el siguiente,

for(;;);

Prowl muestra también un aviso diciendo claramente que el bucle definido es infinito.

En el bucle for es posible utilizar el operador coma. Es decir, podemos tener varias sentencias en la inicialización, condición e iteración. El operador coma garantiza que el operando de su izquierda se ejecutará antes que el operando de su derecha. Si tenemos  varias sentencias en la condición, se tienen que cumplir ambas para no salir del bucle. Un ejemplo del operador coma es el siguiente:

object ej_for3 : ConsoleApplication   
        method + doIt(){
            reference x;
            reference y;
        
            for( x=0,y=0; x < 7; x++,y+=2 ){
                System.console.write(x);
                System.console.write("\n");
            }
            return;        
        }
endObject

Como en los casos anteriores, si dentro del cuerpo del bucle for queremos ejecutar varias sentencias, éstas deben ponerse entre llaves:

for(<expr_inicialización>;<condición>;<expr_iteración>)
{
         <sentencia1>;
         ...
         <sentencia3>;
}

Operadores lógicos y de relación condicional

En los apartados anteriores, donde se explicaban los bucles y la sentencia condicional IF, hemos hablado de condiciones, pero no se ha explicado cómo se tienen que construir esas condiciones en Prowl.

Una condición es una expresión condicional cuya sintaxis es la siguiente:

<expresión> <operador_relacional> <expresión>

Los operadores relacionales son símbolos que se usan para comparar dos valores. Si el resultado de la comparación es correcto la expresión considerada es verdadera, en caso contrario es falsa. Por ejemplo, 8>4 (ocho mayor que cuatro) es verdadera, se representa por el objeto True, en cambio, 8<4 (ocho menor que cuatro) es falsa, False.

Los operadores relacionales en Prowl son los siguientes:

Símbolo Operador Ejemplo Significado
== Igualdad a == b ¿a es igual a b?
!= Desigualdad a != b ¿a es diferente de b?
<= Menor o igual que a <= b ¿a es menor o igual a b?
>= Mayor o igual que a >= b ¿a es mayor o igual a b?
> Mayor que a > b ¿a es mayor que b?
< Menor que a < b ¿a es menor que b?

Se debe tener especial cuidado en no confundir el operador asignación con el operador relacional igual a. Las asignaciones se realizan con el símbolo =, las comparaciones con ==.

Además de estos operadores, existen otros, llamados operadores lógicos o a veces juntores, que lo que hacen es unir dos condiciones. Estos operadores lógicos son:

Símbolo Operador Sintaxis Resultado
&& AND <expr1> && <expr2> El resultado es verdadero si ambas expresiones son verdaderas
|| OR <expr1> || <expr2> El resultado es verdadero si alguna expresión es verdadera
! NOT !<expr1> El resultado invierteel resultado lógico de la expresión

AND y OR trabajan con dos operandos (son binarios) y retornan un valor lógico basadas en las denominadas tablas de verdad. El operador NOT actúa sobre un operando (es unario).

El operador lógico AND, utiliza la técnica denominada de cortocircuito. Se van analizando todas las condiciones de izquierda a derecha y si se encuentra alguna que sea falsa, salta fuera del bucle o salta al else (si lo hubiese).

El operador lógico OR, funciona de manera similar. Se van analizando todas las condiciones de izquierda a derecha y si se encuentra alguna que sea verdadera, entra dentro del bucle o dentro del cuerpo correspondiente a la sentencia IF, sin seguir evaluando el resto de subcondiciones.

La consecuencia  de utilizar la técnica de cortocircuito es que pueden existir expresiones condicionales que no se utilicen jamás. Debe ponerse especial cuidado en no incluir, por tanto, ninguna operación necesaria incluidas dentro de estas expresiones.

A parte de los operadores relacionales y lógicos explicados anteriormente, en Prowl existe otro operador. Este operador es el llamado isInstanceOf. IsInstanceOf trabaja con dos operandos. El primer operando debe ser una expresión, referencia o atributo y el segundo operando debe ser un tipo de dato. IsInstanceOf devuelve true o false dependiendo si el primer parámetro hereda o no del segundo parámetro.

Ejemplos de condiciones correctas en Prowl:

    if ( e isInstanceOf ETypeMismatch ) ;
    if ( x<12 ) ;
    if (((x<1)||(x==0)) AND z!=0) ;
    if(! z<4);

Gestor de excepciones

Si se produce alguna excepción a lo largo de la ejecución de un programa en Prowl, el control del programa se dirige al cuerpo de las excepciones, si es que existe, donde allí serán tratadas. La sintaxis es la siguiente:

onException ( <nombre_identificador>) { <instrucciones> }
                 
    Se pone la palabra reservada onException seguida de un identificador entre paréntesis, que es el nombre del objeto que va a contener la excepción, y por último, entre llaves, se ponen todas las instrucciones necesarias para controlar la excepción.

Ejemplo:

    onException( e ) {
            if ( e isInstanceOf EAssert ) {
                System.console.write( e.toString() );
            }
            else System.console.write( "FATAL: Error interno" );
    }

Sentencia RETURN

Return devuelve una referencia como retorno de un método. Debe estar dentro de un método, el cual debe terminar en al menos un return, y como máximo sólo puede tener dos.

Esta sentencia puede ir sola o acompañada. Si el return va sólo, éste devuelve nothing. Por otro lado el return puede ir acompañado de alguna expresión, lo cual devolvería el valor de dicha expresión.

La sintaxis de la instrucción return es la siguiente:

return [<expresión>];

Ejemplo:

return;
return a + b;
return a;
return nombre.concat( " (directivo)\n" );

Tipos de datos básicos

Prowl soporta los siguientes tipos numéricos:
Enteros: Se representan como los tipos de datos int de C.
Números flotantes: Se representan como los tipos de datos double de C.
En Prowl, no se indica si algún atributo o referencia es de tipo numérico. El tipo de los atributos o referencias se infiere del tipo de dato que le asignemos.
Además de los tipos de datos numéricos mencionados anteriormente, Prowl también soporta las cadenas de caracteres.
Las cadenas de caracteres en Prowl, son una secuencia de caracteres encerrados entre comillas dobles.

La librería estándar

Vamos a introducir de manera breve la librería estándar de la máquina virtual Zero. Nos vamos a centrar, en la creación de objetos, el manejo de enteros, flotantes, cadenas y vectores así como también nombraremos algunas de las excepciones más comunes, de manera que nos puedan ser útiles a la hora de programar en Prowl.

Cómo crear objetos

Object es equivalente a Object en Java. Concretamente, se trata de un objeto que siempre está presente, y que es la raíz de la jerarquía de objetos en Zero. Como es la raíz, lógicamente no hereda de ningún otro objeto.

Métodos para la creación de objetos:

Manejo de valores numéricos

Los valores numéricos son representados por los objetos Int y Float. El primero representa a los números enteros y el segundo a los números flotantes. Todos los números son en realidad objetos, incluyendo los literales. Las operaciones más habituales (son métodos en ambos objetos) son las siguientes. Recuérdese que siempre se devuelve un nuevo objeto con el nuevo valor, en lugar de modificar al objeto que ejecuta el método.
Un resumen de las operaciones matemáticas es el siguiente:

  1. sum( x ): Suma el valor de x al valor del objeto y retorna un nuevo objeto con el resultado.
  2. substract( x ): Resta el valor de x al valor del objeto y retorna un nuevo objeto con el resultado.
  3. multiplyBy( x ): Multiplica el valor del objeto por el del argumento y retorna un nuevo objeto con el resultado.
  4. divideBy( x ): Divide el valor del objeto por el del argumento y retorna un nuevo objeto con el resultado.

Las operaciones lógicas son las siguientes:

  1. isLessThan( x ): Devuelve True si x es mayor que el objeto. Devuelve False en otro caso.
  2. isEqualTo( x ): Devuelve True si x es igual que el objeto. Devuelve False en otro caso.
  3. isLessThan( x ): Devuelve True si x es mayor que el objeto. Devuelve False en otro caso.
  4. isGreaterThan( x ): Devuelve True si x es menor que el objeto. Devuelve False en otro caso.

Todos los métodos anteriores se pueden utilizar mediante operadores matemáticos y lógicos en Prowl.

Manejo de cadenas

Las cadenas son representadas por el objeto String. Sobre una cadena pueden realizarse las operaciones habituales (que siempre devuelven un nuevo objeto cadena), de concatenación, por medio de concat( x ), de obtención de los x caracteres a la izquierda, con left( x ) ... etc. Todos los objetos heredan el método toString(), que convierte la información contenida en el objeto a cadena. Si no se redefine este objeto, entonces se hereda el de Object, que tan sólo devuelve el nombre del objeto.

Métodos para el manejo de cadenas:

  1. String left (Int): Devuelve un nuevo objeto String, con los caracteres desde la posición cero, hasta la posición indicada por el argumento, tomados desde la izquierda.
  2. String right (Int): Devuelve un nuevo objeto String, con los caracteres desde la posición cero hasta la indicada por el argumento, tomados desde la derecha.
  3. String getPosition (Int): Devuelve un nuevo objeto String, con el carácter indicado por el argumento.
  4. String sub(Int begin, Int numberOfCharacters) : Devuelve un nuevo objeto String con una subcadena formada por los caracteres indicados por numberOfCharacters, desde la posición begin, pasados como argumentos como Int.
  5. Conditional empty() : Devuelve True si la cadena es vacía, no contiene caracteres. False en otro caso.
  6. Int length() : Devuelve un número entero como objeto Int, reflejando el número de caracteres en la cadena.
  7. String toString(): Se devuelve a sí mismo, no como un nuevo objeto.

Colecciones de objetos

Las colecciones de objetos como vectores son básicas en cualquier programa. Permiten reunir objetos en torno a una única estructura de datos para un acceso más sencillo. Un ejemplo podría ser una lista de nombres, o una agenda de contactos. Existen dos tipos de colecciones: Vector y Map. El primero permite el acceso directo a una lista de objetos enumerada por un índice, mientras que el segundo asocia una cadena (String) con cualquier objeto.
Todas las colecciones de objetos derivan del objeto DataStructure, que incorpora los métodos que suponen el contrato mínimo para cualquiera de ellas. Además, está la posibilidad de utilizar un procesador, es decir un objeto que derive del objeto Processor. Todos los objetos DataStructure incorporan un método process() que acepta como parámetro un objeto de los anteriores. Cuando es llamado, este método aplica el método doIt() a cada uno de los elementos de la estructura.

Map: Es un objeto que siempre está presente, y que proporciona la funcionalidad de los vectores asociativos en Zero. El índice de estos vectores asociativos siempre es una cadena.
Métodos :
  1. Map add(String, Object) : Añade un nuevo elemento al vector asociativo. La cadena, String, consiste en el índice, mientras que el objeto pasado es el elemento en sí.
  2. Map modify(String, Object) : Modifica un elemento introducido previamente. En realidad, consiste en dos llamadas consecutivas, una a delete(), y otra a add().
  3. Object lookUp(String) : Devuelve el objeto guardado previamente, buscando por su índice. En caso de no existir, lanza la excepción EObjectNotFound.
  4. Map delete (String) : Elimina un elemento introducido previamente, buscando por su índice. En caso de no existir, lanza la excepción EObjectNotFound.
Vector: Es un objeto que siempre está presente, y que proporciona la funcionalidad de los vectores en Zero. El objeto Vector no tiene métodos propios, algunos de los métodos que redefine son los siguientes:
  1. Vector add( Object): Añade un objeto al final del Vector
  2. Vector get(Int): Devuelve el objeto en la posición contenido en el objeto Int pasado por parámetro.
  3. Vector put(Int, Object): Modifica el objeto en la posición contenido en el objeto Int pasado por parámetro, con el nuevo objeto. El método modify es equivalente al método put.
  4. Vector delete (Int): Elimina la posición contenida en el objeto Int pasado por parámetro.
  5. Vector insert (Int, Object): Inserta un nuevo objeto en la posición contenida en el objeto Int pasado por parámetro. Esto supone desplazar todos los elementos a la derecha del nuevo elemento una posición.
  6. Vector erase (Int): Elimina la posición contenida en el objeto Int pasado por parámetro. Esto supone desplazar todos los elementos a la derecha del elemento una posición a la izquierda.
  7. Vector clear(): Elimina todas las posiciones del Vector.
  8. Int seqFind(Object):Realiza una búsqueda secuencial por el Vector, empezando desde la posición 0 hasta la última, o hasta que el objeto es encontrado. Esto se hace ejecutando el método isEqualTo() para cada elemento, pasándole el objeto a buscar como parámetro. Devuelve el número de posición si encuentra el objeto, o Nothing, si no lo encuentra.
  9. Int seqFindLast(Object): Realiza una búsqueda secuencial por el Vector, empezando desde la última posición hasta la primera, o hasta que el objeto es encontrado. Esto se hace ejecutando el método isEqualTo() para cada elemento, pasándole el objeto a buscar como parámetro. Devuelve el número de posición si encuentra el objeto, o Nothing, si no lo encuentra.

Manejo de excepciones

Cuando se lanza una excepción, el objeto enviado es una copia de uno de los objetos que derivan de éste.

  1. EObjectNotFound: Este objeto es enviado cuando ocurre algún problema relacionado con encontrar un objeto. Por ejemplo, cuando MSG se ejecuta, si ésta no encuentra el objeto señalado, entonces, se genera una excepción de este tipo.
  2. EMethodNotFound: Este objeto es enviado cuando ocurre algún problema relacionado con encontrar un método en un objeto. Por ejemplo, cuando MSG se ejecuta, si ésta no encuentra el método señalado en el objeto, entonces, se genera una excepción de este tipo.