EL LENGUAJE REXX

    1.- Introduccion.

    REXX  es  un lenguaje de programacion con unas caracteristicas
    sumamente interesantes. Desgraciadamente, y a pesar  de  haber
    cumplido  ya los veinte a#os, no es tan conocido como debiera,
    aunque esto se esta remediando rapidamente, con una  comunidad
    de programadores activa y creciente. Esta peque#a introduccion
    quiere tambien colaborar en este sentido, ya que, al  parecer,
    no  existen  tutores  de  REXX en espa#ol, ni documentacion de
    otro tipo, salvo el sistema de ayuda incluido con los Sistemas
    Operativos de IBM.

    Que es lo que lo hace atractivo ?. Varias cosas. A lo largo de
    los a#os, he desarrollado tres o cuatro peque#os lenguajes  de
    programacion,  y  he adquirido la certeza de que la existencia
    de tipos primitivos de datos es un error.  Un  programador  no
    deberia de estar pendiente de si sus datos van a ser positivos
    o negativos, y de si se van a mover en  tal  rango  o  en  tal
    otro.  Un programador deberia de pensar unicamente en terminos
    de *numeros* y *cadenas de caracteres*.  Pero los  humanos  no
    tenemos  una  idea  intuitiva  de  los numeros, salvo para los
    primeros enteros. 44016 no nos dice nada, pero no nos importa,
    porque  hemos  aprendido  a  manejar  la  *cadena*  "44016", y
    sabemos combinarla con otras cadenas que  tambien  representan
    numeros.  En definitiva, un lenguaje de programacion avanzado,
    cercano a la forma en que pensamos  los  humanos,  deberia  de
    tratar    unicamente   con   cadenas   de   caracteres.   Unas
    representaran palabras o frases, otras representaran  numeros.
    Esto es exactamente lo que hace REXX, y es su primera ventaja.

    La segunda ventaja es una consecuencia directa de la  primera:
    puesto  que  REXX  trabaja  con  cadenas  de  caracteres  para
    representar numeros, la aritmetica es totalmente independiente
    de la plataforma. No solo eso, es configurable por el usuario.
    Se acabaron los mensajes "underflow" u "overflow". Se acabaron
    las  representaciones  aproximadas  de numeros como "1.2E-34".
    Con REXX, factorial de 158 es exactamente 158!.

    Tercera ventaja: puesto que REXX es un  lenguaje  orientado  a
    cadenas,  es de esperar que encontremos una completa remesa de
    funciones que traten cadenas.  Funciones  de  alto  nivel  que
    ahorraran mucho trabajo respecto a otros lenguajes.

    Cuarta  ventaja:  REXX  esta  perfectamente  integrado  con su
    entorno. De hecho, puede usarse  como  lenguaje  de  "script".
    Pero  un  lenguaje  de  "script"  mas potente y facil de usar.
    Puede pasarse informacion de un programa REXX a otro  y  entre
    programas  REXX  y  programas  escritos  en otros lenguajes de
    programacion, como C o Java.

    Quinto: el tratamiento que hace REXX de los arrays es original
    y  sumamente  elegante.  Tanto es asi, que arrays de cualquier
    dimension y estructuras definidas por el usuario son cubiertos
    por  la  misma  sintaxis:  clara  y  natural.  Tanto,  que los
    `record' de Pascal y `struct'  de  C  de  pronto  nos  parecen
    anticuados.

    Por lo demas, REXX es un lenguaje estructurado en bloques, con
    instrucciones de  control  de  flujo  algo  mas  elaboradas  y
    flexibles de lo que podemos encontrar en otros lenguajes.

    Quizas   sea  tambien  preciso  decir  que  REXX  no  contiene
    punteros. Un alivio para los primerizos y una perdida para los
    programadores  mas  avezados,  amantes  de  las estructuras de
    datos complejas que con ellos pueden crearse.

    Finalmente, tengo que  anunciarte  que  REXX  es  un  lenguaje
    multiplataforma,  unas  diez  veces  mas facil de aprender que
    Java.  Escribe un programa en REXX y ejecutalo  sin  tocar  un
    punto  ni una coma en cualquier maquina que te encuentres y en
    cualquier sistema operativo.  Interesante,  verdad  ?.  Si  te
    parece  poco,  existe  una  extension  de  REXX  que introduce
    objetos, Object REXX, y otra  extension  llamada  NetREXX  que
    permite  escribir  aplicaciones para la RED y aprovechar todas
    las ventajas de los entornos Java.  Y  hablando  de  entornos,
    puedes  programar REXX tanto en modo consola, usando tu editor
    favorito, como en modo grafico, usando los entornos  gratuitos
    que  existen  para  este  lenguaje. Y en cuanto al ejecutable,
    igual:  puedes  crear  con  la  misma  facilidad  aplicaciones
    orientadas a consola y aplicaciones graficas.

    Por supuesto, tambien tiene algunas desventajas. La principal,
    es que de momento no existen compiladores gratuitos para REXX.
    Sin  embargo,  los interpretes que existen funcionan muy bien,
    pudiendo procesar, en un Pentium 500,  del  orden  de  600.000
    clausulas por segundo. Juzga tu mismo.

    2.- Para aprender REXX

    Bueno,  lo  primero  que  necesitas  es un interprete REXX. En
    algunas distribuciones Linux puedes encontrar el interprete de
    Regina, y si miras en la direccion:

    http://www2.hursley.ibm.com/rexx

    encontraras  una  lista  de enlaces a Implementaciones de REXX
    gratuitas. BREXX, por ejemplo, esta bien, asi que aqui  tienes
    el  enlace  para  descargar  la version para DOS: ( que puedes
    correr en cualquier Windows, por supuesto )

    ftp://gwdg.de/pub/languages/rexx/brexx

    Lo segundo que necesitas es un  editor.  Supongo  que  conoces
    algun  otro  lenguaje  de programacion, de manera que hare una
    exposicion inversa a la habitual, es decir, de arriba a abajo.
    Asi  que, a medida que vayas leyendo esta introduccion, teclea
    los peque#os programas y ejecutalos.  Es la forma mas  rapida.
    Por  cierto,  que  me voy a limitar a la implementacion que la
    comunidad REXX llama `clasica'. No hablare nada de  ObjectREXX
    ni de NetREXX.

    Por   otra   parte,  yo  uso  dos  implementaciones  de  REXX,
    principalmente, la primera, PC-DOS REXX, y la segunda,  Regina
    REXX,  para  Linux.  Mi  fuente  mas  completa de informacion,
    aparte algunos excelentes tutores ( *no* tutoriales, burros  )
    que pueden conseguirse en la RED, es el manual de PC-DOS REXX,
    de IBM. Es posible por tanto que algun punto de los explicados
    en  lo  que sigue sea especifico de esta implementacion. Si es
    asi, mira en la documentacion que acompa#ara a tu  interprete.

    3.- Estructura de un programa

    Como  he  dicho antes, voy a hacer una exposicion inversa a lo
    habitual, empezando por las estructuras mayores y descendiendo
    a  las  mas peque#as. Pero como comprendo tu impaciencia, aqui
    va el programa con que se comienza la exposicion de  cualquier
    lenguaje desde hace 30 a#os:

    /* primer programa en REXX */
    say "Hola mundo!"

    Editalo,  salvalo  con  el  nombre  1.r  u  otro  cualquiera y
    ejecutalo llamando al interprete. Si este se  llamase  `rexx',
    escribe en tu linea de comandos `rexx 1.r'.

    Un  programa  REXX comienza con un comentario y termina con la
    ultima linea del archivo fuente, o la instruccion `EXIT':

    /* patron de un programa */
      instrucciones
    exit

    Dentro del bloque principal del programa puede haber  llamadas
    a subrutinas. Una subrutina es un segmento de codigo que puede
    ser llamado desde mas de un lugar en  el  programa  principal.
    Este fragmento de codigo puede residir en el mismo archivo que
    el programa principal o en un archivo de disco  distinto.  Una
    subrutina  se  invoca  mediante  la  instruccion  `CALL',  por
    ejemplo:

    /* patron de un programa */
      instruccion 0
      instruccion 1
      ...
      CALL mirutina
      instruccion n
      instruccion n+1
      ...
    EXIT
    mirutina:
      instrucciones
    RETURN

    Opcionalmente, despues de RETURN puede ir  una  expresion.  Si
    este  es  el  caso,  la  expresion  es  evaluada,  y  el valor
    resultante asignado a una variable  especial  llamada  RESULT,
    que  puede ser usada en el programa principal. Si la expresion
    no aparece, el valor que pudiese  tener  previamente  asignado
    RESULT  es  eliminado,  de  manera  que  su evaluacion da como
    resultado el nombre "RESULT".

    En cuanto al paso de argumento, si es preciso, se colocan tras
    el nombre de la rutina, separados por comas. Por ejemplo:

    CALL mirutina a,b,c

    Para  que  estos  argumentos sean tomados efectivamente por la
    subrutina, y pasados a variables formales con las que  operar,
    se usan las instrucciones `ARG' y `PARSE ARG'. La primera pasa
    los argumentos a mayuscula, antes de entregarlos a la  rutina,
    mientras que la segunda los pasa exactamente. Bien, es la hora
    de un ejemplo:

    /* programa principal               */
    /* calculo del area del triangulo   */
    say "introduce la altura del triangulo: "
    pull altura
    say "introduce la base del triangulo: "
    pull base
    call area base,altura
    say "el area del triangulo es : "
    say RESULT
    exit
    /* subrutina que calcula area        */
    area:
    parse arg a,b
    return 0.5*a*b

    Otra forma de pasar argumentos a una subrutina es mediante  la
    funcion  ARG(). En esta modalidad, se llama a la subrutina con
    CALL, y  los  argumentos  pueden  ser  expresiones  que  seran
    computadas con ARG(). Por ejemplo:

    /* ejemplo del uso de ARG() */
    say "introduce en numero: "
    pull a
    say "introduce un segundo numero: "
    pull b
    call mirutina a+b,a-b,a*b
    say RESULT
    exit
    mirutina:
    f1=arg(1)
    f2=arg(2)
    f3=arg(3)
    return f1*f2*f3

    En REXX existe el concepto de `funcion' ademas del concepto de
    subrutina.  A las funciones se las llama por  su  nombre,  con
    los argumentos entre parentesis, y *deben* devolver siempre un
    valor mediante RETURN, aunque sea la  cadena  nula.  El  valor
    devuelto  es  usado  para  evaluar  la  expresion de la que la
    llamada a la funcion forme parte. Por ejemplo:

    /* ejemplo del uso de funciones en REXX */
    say "introduce un numero: "
    pull a
    say "introduce otro numero: "
    pull b
    say "tres veces el producto es: " 3*producto(a,b)
    exit
    producto:
    parse arg a,b
    return a*b

    Resumiendo lo expuesto hasta aqui, la estructura al  mas  alto
    nivel  de  un  programa  REXX  tiene  la  forma  de  la figura
    siguiente:

    Bloque principal            Bloques de subrutinas
    +-----------------+         +--------------------+
    |  call a  ---------------> | subrutina a        |
    |   .             |         +--------------------+
    |   .             |
    |   .             |         +--------------------+
    |  call b  ---------------> | subrutina b        |
    |   .             |         +--------------------+
    |   .             |
    |   .             |         +--------------------+
    |  call c  ---------------> | subrutina c        |
    |   .             |         +--------------------+
    |   .             |
    |   .             |
    |   .             |
    |                 |
    |  exit           |
    +-----------------+

    Conviene en este punto hacer una reflexion. REXX solo tiene un
    tipo  de  datos,  la  cadena  de  caracteres, y por eso muchos
    afirman que, en consecuencia, las variables no  necesitan  ser
    declaradas.   Este  es  un error comun, ya que no hay nada que
    impida que las variables no declaradas tengan  tipo,  ni  nada
    que  impida  que  variables  sin tipo deban ser declaradas. En
    cualquier caso, en REXX no es necesario declarar variables, lo
    que  podria  llevar  a  errores de dificil localizacion, sobre
    todo en programas largos.  Esencialmente, el problema consiste
    en  que  todas  las variables son globales. Para remediar esta
    situacion, REXX ofrece la posibilidad de ocultar las variables
    de  subrutinas  al  resto  del programa, haciendolas por tanto
    locales.  Si  se  desea  hacer  esto,  bastara  con   escribir
    `PROCEDURE'  al  inicio de la subrutina. Esta instruccion solo
    puede llamarse una vez, y debe ser la primera  instruccion  de
    la  rutina.  Una  variante  de  esta instruccion es `PROCEDURE
    EXPOSE', que permite compartir con el programa  principal  una
    porcion  de las variables de la rutina. Su sintaxis es simple:
    a continuacion de `PROCEDURE EXPOSE' se escriben  los  nombres
    de  las  variables  que  vayan a ser compartidas. Esto incluye
    aquellas que sean creadas dentro de la subrutina, que pasan  a
    estar disponibles para el programa principal.

    Otra  funcion  interesante  es  SYMBOL(),  que toma como unico
    argumento un nombre de variable entre dobles comillas  (  para
    distinguirlo  de  su valor ). SYMBOL() devuelve `BAD', `LIT' o
    `VAR' segun que el argumento no  sea  un  simbolo  valido,  el
    argumento sea un nombre valido de variable al que aun no se ha
    asignado ningun valor ( o una constante numerica )  o  que  el
    nombre  haya  sido usado ya como variable en el programa.  Una
    de las utilidades de SYMBOL() puede ser asegurarse de que  una
    variable ha sido inicializada antes de usarla.

    4.- Estructuras de control y bloques

    REXX  es  un  lenguaje  estructurado  de  bloques. Los bloques
    comienzan con `do' y terminan con `end'. Por ejemplo:

    do
      instruccion 1
      instruccion 2
      ...
    end

    que es similar a la pareja `begin'/`end' de Pascal  o  `{'/`}'
    de  C.   Existen  estructuras de control que permiten ejecutar
    repetidas veces un bloque de instrucciones, y estructuras  que
    permiten elegir entre varios bloques, para ejecutar uno u otro
    segun se den o no ciertas condiciones.

    4.1.- Estructuras repetitivas

    Veras que REXX destaca sobre otros lenguajes en la riqueza  de
    las  estructuras  repetitivas, que hacen muy dificil encontrar
    un caso en que la formulacion de un problema no siga el camino
    que  cualquiera  consideraria `natural'. Veamos cuales son las
    posibilidades:

    i)  Bloque  repetitivo  simple.  Es  el  caso  mas   sencillo.
    Simplemente,  se  especifica  el  numero de veces que se desea
    ejecutar el bloque.  Por ejemplo:

    do 10
        say "hola"
    end

    ii) Uso de un contador para usar en el interior del bloque:

    do i=1 to 10
        say "hola" i
    end

    iii) Uso de contador y especificacion del intervalo. Puede  de
    esta  forma  controlarse  el  incremento  que  experimenta  el
    contador en cada iteracion:

    do i=1 to 10 by 2
        say "hola" i
    end

    iv) Uso de contador, especificacion del intervalo y numero  de
    iteraciones:

    do i=1 to 10 by 2 for 2
        say "hola" i
    end

    Y en este caso, el bucle solo se ejecuta dos veces.

    v) Repeticion del bloque un numero ilimitado de veces:

    do forever
        say "hola"
    end

    vi) Uso de expresiones condicionales con `while'. Por ejemplo:

    /* leer numeros mientras sean distintos de cuatro */
    numero=0
    do while numero = 4
        pull numero
    end

    En esta construccion, la condicion es comprobada al  principio
    del bucle.

    vii)   Uso  de  expresiones  condicionales  con  `until'.  Por
    ejemplo:

    /* leer numeros hasta que se introduzca el 4 */
    numero=0
    do until numero = 4
        pull numero
    end

    En esta construccion, la condicion es comprobada al final  del
    bucle.  Esto  significa  que  el  bloque  de  instrucciones es
    ejecutado al menos una vez.

    viii) Variable de control  y  condicion.  Esta  es  una  forma
    realmente sofisticada de bucle repetitivo. Sirva este ejemplo:

    /*
    No aceptar un "NO" por respuesta, salvo si el "NO"
    se repite tres veces
    */
    do 3 until respuesta = "NO"
        pull respuesta
    end

    En este ejemplo, el bucle se ejecuta hasta  que  la  respuesta
    sea  distinta  de  "NO",  o,  caso  de  ser  "NO", tres veces.
    Ejecutalo y lo veras mas claro. Otro ejemplo:

    /*
    Leer 10 numeros, pero salir si se introduce la cadena vacia
    */
    do 10 until respuesta = ""
        pull respuesta
    end

    4.2.- Una piedra en el camino

    A veces, el flujo normal del programa debe  ser  interrumpido.
    Estrictamente,  siempre  es posible preservar la estructura de
    bloques de un programa. Lo que ocurre es que a veces  esto  no
    es  practico.  Piensa  en  el  caso en que has de explorar una
    matriz tridimensional buscando que un elemento tenga un  valor
    determinado.   Necesitaras  tres  bucles  anidados,  y  cuando
    encuentres  el  elemento  que  buscas,  que  haces  ?.  Puedes
    guardarte  los  indices de la matriz y el valor, esperar a que
    concluyan las iteraciones y despues seguir  la  ejecucion  del
    programa.  Si  la  matriz  es de 100 por 100 por 100, tiene un
    millon de elementos, y si el que buscas es el primero,  tienes
    que  explorar  999.999 antes de terminar, lo que es un notable
    desperdicio. Mejor salir  del  bucle  inmediatamente,  y  para
    estos   casos,   incluso   los   lenguajes  que  abominan  mas
    abiertamente de  la  instruccion  `goto',  como  Pascal  o  C,
    *tienen*  la  instruccion  `goto'.  Donde otros lenguajes usan
    `goto', REXX usa `SIGNAL'. Su uso es muy simple.  He  aqui  un
    ejemplo  donde  se busca un determinado elemento en una matriz
    de 100*100:

    do i=1 to 100
        do j=1 to 100
            if a.i.j = 3.14 then
                signal salir
        end
    end
    salir:

    Pero `SIGNAL' tambien puede usarse para colocar `trampas',  de
    manera  que el programa interrumpa su ejecucion normal y salte
    a una  rutina  especifica  cuando  se  produce  una  condicion
    determinada.   La  sintaxis  es  `SIGNAL  ON condicion', donde
    `condicion' puede ser:

    ERROR: se produce el salto a la subrutina ERROR:  cuando  REXX
    pasa  una  cadena  al  Sistema  para  que  la  ejecute  y como
    resultado el sistema devuelve un error.  (  REXX  puede  pasar
    cadenas  al  entorno,  y  por  eso es un excelente lenguaje de
    `script'. )

    FAILURE: se produce el salto a la  subrutina  FAILURE:  cuando
    REXX  pasa una cadena al Sistema para que la ejecute pero este
    encuentra un error grave y no la ejecuta

    NOTREADY: se produce el salto a la subrutina NOTREADY:  cuando
    hay algun error de Entrada/Salida

    NOVALUE:  la condicion de error consiste en que REXX encuentra
    un nombre que podria ser el nombre de una variable  pero  esta
    en   realidad   no  existe.  Por  ejemplo,  este  error  puede
    producirse  al  teclear  incorrectamente  el  nombre  de   una
    variable,  y  podria ser muy dificil de localizar a no ser por
    esta facilidad de REXX.

    SYNTAX: se ejecuta la subrutina llamada  SYNTAX:  cuando  REXX
    encuentra un error sintactico mientras procesa un programa

    Cualquiera   de  estas  trampas  puede  desactivarse  mediante
    `SIGNAL OFF nombre'.

    Otra interesante posibilidad consiste en reiniciar el programa
    caso  de  producirse  un  error.  La  sintaxis es similar pero
    usando  la  instruccion  `CALL'.  Asi   `CALL   ON   NOTREADY'
    reiniciaria  el  programa si durante su ejecucion se produjese
    un error de Entrada/Salida.

    Hay otras dos instrucciones utiles  en  segun  que  casos.  La
    primera  es  `LEAVE',  y  permite  salir  inmediatamente de un
    bucle. Podria usarse `SIGNAL', pero mientras que esta  provoca
    que  el  programa continue ejecutandose en cualquier punto que
    se desee, `LEAVE' salta inmediatamente despues del  final  del
    bucle  actual.  Ademas,  cuando  hay  mas de un bucle anidado,
    puede especificarse de cual de ellos se desea salir. No  tiene
    por que ser el mas interno. Por ejemplo, el siguiente programa
    imprime parejas (i,j), pero pasa a la `i' siguiente en  cuanto
    encuentra el valor j=3 y ademas i=2:

    do i=1 to 5
        do j=1 to 5
            if i=2 & j=3 then leave j
            say i j
        end
    end

    Similar  a  la instruccion anterior es `ITERATE', que salta al
    principio del bucle. Asi, el programa anterior podria  haberse
    escrito en la forma:

    do i=1 to 5
        do j=1 to 5
            if i=2 & j=3 then iterate i
            say i j
        end
    end

    4.3.- Condicionales

    La  construccion  condicional  en  REXX es similar a la que se
    encuentra en BASIC o Pascal.  Por  ejemplo,  en  el  siguiente
    programa se pide la introduccion de un numero, y se ejecuta un
    bloque de instrucciones u otro segun este numero cumpla  o  no
    una condicion:

    say "introduce un numero: "
    pull a
    if a>0 then
        do
            say "el numero es mayor que cero"
            say "adios, hasta la proxima"
        end
    else
        do
            say "el numero es menor o igual que cero"
            say "que pases buen dia"
        end

    Puede,  en  determinadas  circunstancias, no ser preciso hacer
    nada.  Entonces, se usara la instruccion `NOP', que  significa
    `No OPeration'. Por ejemplo:

    say "introduce un numero: "
    pull a
    if a>0 then
        do
            say "el numero es mayor que cero"
            say "adios, hasta la proxima"
        end
    else
        nop

    Naturalmente,  los  condicionales pueden anidarse entre si, de
    manera que, eligiendo  alternativamente  entre  dos  opciones,
    podemos  optar por un numero arbitrario de opciones. Pero para
    eso es mejor la construccion `SELECT', cuya estructura  es  la
    siguiente:

    select
        when condicion_0 then
        do
            instrucciones
        end
        when condicion_1 tnen
        do
            instrucciones
        end
        otherwise
        do
            instrucciones
        end
    end

    4.4.- Operadores condicionales

    Existen  muchos  operadores condicionales. Algunos de ellos ya
    los he presentado en distintos fragmentos de codigo.  Aqui  va
    la lista completa de ellos:

    =   igual
    <   menor
    >   mayor
    >=  mayor o igual
    <=  menor o igual
    <>  distinto

    todo  ellos  pueden  negarse con el operador `', teniendo, por
    ejemplo: = ( no igual ), <  (  no  menor  que  ),  etc.  Estos
    operadores  comparan  numeros  y  cadenas de caracteres. En el
    segundo caso, los espacios en blanco al principio y  al  final
    de  las  cadenas  no se tienen en cuenta a la hora de hacer la
    comparacion. Existe una version `fuerte' de estos  operadores,
    que  hacen  comparaciones  estrictas  de  cadenas,  es  decir,
    incluyendo los posibles espacios en blanco.  Estos  operadores
    son:

    ==   igual
    <<   menor
    >>   mayor
    >>=  mayor o igual
    <<=  menor o igual

    y sus correspondientes negaciones mediante `'. Otra diferencia
    entre estos operadores y  los  anteriores  esta  en  la  forma
    distinta  en  que  tratan cadenas que representan numeros. Por
    ejemplo, 2 y 2.0 no son la misma cadena,  pero  son  el  mismo
    numero. Ejecuta para probar el siguiente fragmento de codigo:

    /* operadores de comparacion */
    a=2
    b=2.0
    if a==b then say "la misma cadena"
    if a=b  then say "los mismos numeros"

    5.- Simbolos compuestos

    El tratamiento que hace REXX de lo que en Pascal son registros
    y en C estructuras es muy interesante, porque, ademas, de  una
    forma  unificada  se  tratan tambien las estructuras definidas
    por el usuario y los arrays de datos. Por cierto,  REXX  lleva
    la cuenta del tama#o de los arrays, ampliandolo si es preciso,
    de manera que  nunca  tendremos  mensajes  del  tipo  `out  of
    range'.

    Un  simbolo compuesto es un simbolo que tiene una `raiz' y una
    o varias extensiones, separadas por un punto. A la comprension
    por el ejemplo:

    /* programa REXX que inicializa un array de 20 numeros */
    do i=1 to 20
        a.i=0
    end

    /* programa REXX que inicializa una matriz de 4*4 */
    do i=1 to 4
        do j=1 to 4
            a.i.j=0
        end
    end

    El  uso  de  simbolos  compuestos  para  formar  registros con
    informacion relacionada tambien es directo.

    /* ilustracion del uso de simbolos compuestos */
    pull a.nombre
    pull a.direccion
    pull a.telefono
    pull a.dni
    say a.nombre
    /* ilustracion de un array de estructuras */
    do i=1 to 10
        pull a.i.nombre
        pull a.i.direccion
        pull a.i.edad
    end
    do i=1 to 10
        say a.i.edad
    end

    6.- Aritmetica

    6.1.- Aritmetica basica con REXX

    La aritmetica es una de las partes mas gratificantes de  REXX.
    No  hemos  de preocuparnos de los rangos en que se moveran las
    cantidades,  ni  de  si  llevaran  o  no  signo,  ni  de   los
    moldeadores  de  tipo,  ni  de  la  imposibilidad  de  obtener
    resultados exactos cuando los numeros  son  muy  grandes.  Por
    ejemplo,  trata  de  hallar  factorial  de 100 con tu lenguaje
    favorito. No puedes. Con REXX si, miralo:

    /* factorial grande */
    f=1
    do i=1 to 100
        f=f*i
    end
    say f

    Ejecutalo  y  obtendras  el  resultado:  9.33262137E+157.  Por
    defecto,  REXX  usa  nueve  cifras  significativas, pero puede
    cambiarse a cualquier otra cantidad. Por ejemplo, si necesitas
    doscientos  digitos,  escribe  `numeric  digits  200' antes de
    comenzar los calculos, y vuelve a ejecutar el programa:

    93326215443944152681699238856266700490715968264381621468592963
    89521759999322991560894146397615651828625369792082722375825118
    5210916864000000000000000000000000

    Esto es factorial de 100!. Por lo demas, REXX se comporta  con
    la  aritmetica  como es de esperar. El siguiente programa toma
    tres numeros y hace algunas operaciones con ellos:

    /* algunas operaciones */
    pull a
    pull b
    pull c
    say a+b a-b a/b a**b (a+b)/(a+c) a%c a//c

    Ademas  de  los  operadores   habituales   de   suma,   resta,
    multiplicacion  y division, tenemos la exponenciacion (**), la
    division entera (%) y el resto (//).

    6.2.- numeric digits

    Como se ha  dicho  antes,  REXX  trabaja  internamente  con  9
    digitos  significativos. Pero esta cantidad puede alterarse en
    cualquier momento para satisfacer  necesidades  especiales  de
    calculo.  Por ejemplo, en calculos con numeros enteros grandes
    deseariamos representaciones exactas, sea cual sea  el  numero
    de digitos que se requieran.

    Naturalmente, usar un elevado numero de digitos significativos
    tiene su precio en tiempo de calculo. A veces, este precio  no
    compensa  de la perdida de prestaciones, mientras que otras es
    imprescindible. Cada caso debera ser  valorado,  pero  podemos
    hacernos  una  idea  con  el siguiente programa que calcula el
    tiempo necesario para realizar una operacion sencilla,  con  9
    digitos y con 18 digitos:

    /* perdida de velocidad al aumentar numero de digitos */
    a=1
    b=7
    t=time('E')
    do i=1 to 100000
        c=a/b
    end
    say time('E')
    t=time('R')
    numeric digits 18
    do i=1 to 100000
        c=a/b
    end
    say time('E')

    En  mi maquina, el primer bucle se ejecuta en 3.40 segundos, y
    el segundo en 4.50. Digamos que  la  funcion  `time'  tiene  8
    formatos  distintos,  y  que  nosotros  hemos usado las formas
    time('E')  y  time('R').  La  primera  pone   en   marcha   un
    cronometro,   si   no  esta  ya  corriendo,  o  da  el  tiempo
    transcurrido, en caso contrario. La segunda pone el cronometro
    a  cero  y lo inicia. Por ultimo, la funcion digits() devuelve
    el valor que fue establecido con `numeric digits'.

    6.3.- numeric fuzz

    Imaginemos   que   deseamos   sumar   1/3   +   1/3   +   1/3.
    Independientemente  de  cual  sea  la precision elegida, nunca
    obtendremos 1, sino algo como 0.999999999.  O  imaginemos  que
    obtenemos  dos numeros, procedentes de dos calculos distintos.
    Uno  de  ellos  puede  ser  0.2324256  y  el  otro  0.2324356.
    Obviamente  no  son  iguales, o si ?. Depende de que precision
    usemos para las comparaciones. Dependiendo de  que  aplicacion
    estemos  ejecutando,  dos  numeros  como los anteriores pueden
    ser, o no, considerados como iguales. Este detalle se controla
    mediante  `numeric  fuzz n', donde n es un entero entre 0 y el
    valor que este usando `numeric digits' menos 1.  Por  defecto,
    vale  0,  lo  que  indica  que  no se descartara ningun digito
    durante la comparacion. Un valor distinto de cero  indica  que
    numero  de  digitos  seran  descartados cuando se realizen las
    comparaciones. Considerese el siguiente ejemplo:

    /* efecto de numeric fuzz */
    numeric fuzz 0
    say 1 + 1/3 + 1/3 + 1/3 = 2      /* '0', falso */
    numeric fuzz 1
    say 1 + 1/3 + 1/3 + 1/3 = 2      /* '1' cierto */

    6.4.- La funcion format()

    Aunque no es estrictamente aritmetica, incluyo en  este  punto
    una  descripcion de la funcion format(), que controla la forma
    en que se imprimiran en pantalla los numeros. En su forma  mas
    simple,  format()  toma  tres  argumento:  el  numero que sera
    impreso, el numero de espacios antes del punto  decimal  y  el
    numero  de espacios despues del punto decimal. Si el numero de
    espacios antes del punto es insuficiente, se genera un  error.
    Si  el  numero de espacios solicitados excede al necesario, se
    a#aden espacios en blanco. En cuanto al espacio tras el  punto
    decimal,  si  se ha solicitado un espacio mayor del necesario,
    se a#aden ceros hasta completar ese espacio, y si  el  espacio
    es insuficiente, el numero se redondea. Ejemplo:

    /* uso de format() */
    a=3.1415
    say format(a,1,1)
    say format(a,1,6)
    say format(a,4,4)

    Es  posible  usar  format()  para obtener salidas numericas en
    formas de columna, que son mas faciles de  leer.  Si  format()
    necesita  descartar  algunos  decimales, realizara un redondeo
    convencional, pero si lo que deseamos es truncar, usaremos  la
    funcion  trunc(),  donde  se  especifica  el  numero de cifras
    decimales que  se  usaran.  La  diferencia  entre  format()  y
    trunc() se ve claramente en el siguiente fragmento de codigo:

    /* diferencia entre trunc() y format()
    a=3.145
    say format(a,1,2)
    say trunc(a,2)

    7.- Entrada/Salida con REXX

    7.1.- Escribir/Leer lineas

    Hay dos diferencias fundamentales entre REXX y otros lenguajes
    de programacion, que  lo  hacen  muy  agradable  de  usar.  La
    primera, es que el interprete lleva cuenta de que archivos hay
    abiertos, y se ocupa de cerrarlos al terminar el  programa  si
    es  que  no  se  hace explicitamente. Por tanto, no es preciso
    abrir explicitamente un archivo para poder usarlo. La segunda,
    es que en REXX los archivos son meras secuencias de bytes, con
    una marca que indica el final de cada cadena y que no  es  mas
    que  un  byte especifico. Por tanto, no hay archivos con tipo.
    Se escriben y se leen caracteres individuales,  o  cadenas  de
    ellos.  Conceptualmente  es la misma idea que tiene el sistema
    UNIX de lo que son archivos.

    Comenzamos con un ejemplo sencillo, un programa que  lee  este
    archivo y lo muestra en pantalla:

    /* uso de archivos */
    archivo = "rexx2.txt"
    buffer="-"
    linein(archivo,1,0)
    do while buffer = ""
        a=linein(archivo,,1)
        say a
    end

    La  funcion  encargada  de  leer  una  linea  de un archivo es
    `linein()', que toma tres argumentos: el nombre  del  archivo,
    la  linea  que va a leerse y el numero de lineas que se desean
    obtener.  Si el archivo no esta abierto, la  primera  llama  a
    linein() lo abre, y coloca a 1 la variable que lleva la cuenta
    de que linea es la siguiente  que  se  va  a  leer.  Asi,  una
    llamada  como  linein(archivo,1,0) abre el archivo, inicializa
    el contador de lineas a 1 *pero no lee ninguna linea*, de  ahi
    el  valor  0 en el tercer parametro. En sucesivas lecturas, la
    variable que indica la siguiente linea  a  leer  se  actualiza
    automaticamente,  de  manera  que  no  hay  que especificarla.
    Naturalmente, en cualquier  momento  puede  especificarse  que
    linea  se  desea  leer. Si es la linea `n', basta con escribir
    linein(archivo,n,1). Como segundo ejemplo, veamos un  programa
    que  lee  las  lineas  de  este  archivo  y a continuacion las
    escribe en orden inverso:

    /* leer y escribir en orden inverso */
    contador=0
    buffer="-"
    archivo="rexx2.txt"
    linein(archivo,1,0)
    do contador=1 by 1
       a.contador=linein(archivo,,1)
       if a.contador="" then leave
    end
    do contador2=contador to 1 by -1
        say a.contador2
    end

    Observese por otra parte que un error  de  lectura,  producido
    por  querer  leer  mas  alla  de  los  limites del archivo, se
    traduce en que linein() devuelve la cadena nula.

    En cuanto a la escritura de archivos, se efectua  mediante  la
    funcion  lineout(),  que  toma  como  parametros el nombre del
    archivo, la cadena que se desea escribir  y  la  posicion.  El
    valor devuelto es 0 si todo fue bien o 1, si hubo algun error.
    Una primera llamada a lineout() abre  el  archivo,  si  no  se
    encontraba  abierto, y coloca el puntero de escritura al final
    del archivo, de manera que la siguiente lectura se a#adira  al
    archivo.  Tengase  en  cuenta  que  si  se especifica un lugar
    concreto para escribir la cadena, y la cadena que ocupa  tiene
    ese  lugar  tiene  una  longitud  menor  de  la  que  pensamos
    escribir,  los  datos  del  archivo  pueden  corromperse.   En
    entornos  DOS,  lineout()  a#ade  un caracter nueva linea y un
    caracter de retorno de  carro  al  final  de  cada  linea.  El
    siguiente  ejemplo  lee  este  archivo  y  lo escribe en orden
    inverso en el archivo llamado con justicia "inverso.txt":

    /* leer un archivo y escribir en otro en orden inverso */
    contador=0
    buffer="-"
    entrada="rexx2.txt"
    salida="inverso.txt"
    linein(entrada,1,0)
    do contador=1 by 1
       a.contador=linein(entrada,,1)
       if a.contador="" then leave
    end
    /* escribir en orden inverso */
    do contador2=contador to 1 by -1
        call lineout salida, a.contador2
        if RESULT=1 then leave
    end
    lineout(salida)
    Observese que no hay llamada previa a lineout(). En la primera
    de  ellas,  el  archivo  es  abierto y el puntero de escritura
    colocado. Por otra parte,  se  observara  tambien  que  se  ha
    llamada  como  subrutina,  para  disponer  del resultado de la
    operacion en la variable RESULT y decidir si abandonar o no el
    bucle. Igualmente podria haber escrito:

        resultado=lineout(salida,a.contador2)
        if resultado=1 then leave

    Finalmente,  observese  como  se  ha cerrado explicitamente el
    archivo de salida, aunque no hubiese sido necesario.

    Existen las funciones homologas de linein() y  lineout()  para
    trabajar a nivel de caracteres, no de lineas, y son charin() y
    charout().    Ademas,   existen   algunas   otras    funciones
    miscelaneas  que  pueden  ser  utiles.  Por  ejemplo,  lines()
    devuelve 1 si quedan lineas por leer de un  archivo,  y  0  en
    caso   contrario.   La   funcion   stream()   permite  obtener
    informacion sobre  un  archivo,  pero  su  descripcion  merece
    espacio aparte.

    7.2.- La funcion stream()

    La  sintaxis  de stream() es stream(nombre,operacion,comando).
    `nombre' es el nombre del archivo. `operacion' puede  ser  una
    de  las  letras `C', `S' o `D'. El primer caso indica que va a
    ejecutarse un comando sobre el archivo, y *solo* en este  caso
    se proporciona el tercer argumento, que puede ser uno de entre
    OPEN, CLOSE, SEEK. OPEN  sirve  para  abrir  el  archivo.  Por
    defecto,  en  modo  de lectura/escritura. Si se desea abrir un
    archivo para solo lectura se puede a#adir la palabra `READ', o
    `WRITE'  si  el archivo sera de solo escritura. `CLOSE' cierra
    el  archivo.  Finalmente,  `SEEK'   coloca   el   puntero   de
    lectura/escritura  en  la posicion especificada. Esta posicion
    puede contarse a partir de  la  posicion  actual  (adelante  o
    atras),  desde  el  inicio  del  archivo  o desde el final del
    archivo. Algunos ejemplos:

    stream(nombre,'C','seek =2')   /* puntero en la segunda linea */
    stream(nombre,'C','seek <2')   /* puntero en la segunda linea */
                                   /* contando desde el final     */
    stream(nombre,'C','seek +2')   /* puntero dos lineas adelante */
    stream(nombre,'C','seek -2')   /* puntero dos lineas atras    */

    Otras posibilidades son preguntar si  el  archivo  existe,  su
    tama#o o su fecha. Estos son los ejemplos:

    stream(nombre,'C','query exists')
    stream(nombre,'C','query size')
    stream(nombre,'C','query datetime')

    Y el resultado es una cadena con la informacion pedida.

    Por su parte, `S', devuelve una cadena indicando el estado del
    archivo. Por ejemplo:

    stream(nombre,'S')

    y la  salida  puede  ser  `ERROR',  `NOTREADY',  `READY'  y  '
    UNKNOWN'.  `D'  finalmente,  es  identico  a `S', salvo que se
    a#ade informacion adicional.  En  definitiva,  estas  son  las
    posibilidades de stream():

    stream(nombre,'C',open | close | seek +|-|=|< offset | query
                 ,'S'
                 ,'D'

    8.- La cola

    Para REXX, cola tiene un significado distinto del habitual. En
    realidad, es una pila, solo que, al contrario que en las pilas
    convencionales,  la informacion puede apilarse sobre el ultimo
    elemento de la pila o introducirse bajo el  primero.  De  esta
    forma,  esta  estructura  particular puede funcionar como pila
    LIFO y como pila FIFO simultaneamente. El tratamiento que hace
    REXX  de esta pila tambien es particular, aunque depende de la
    implementacion.  En las implementaciones mas sofisticadas,  la
    cola  puede persistir aun despues de haber acabado un programa
    REXX, y quedar disponible para otros programas, bien  escritos
    en  REXX,  bien  escritos en cualquier otro lenguaje, como C o
    Pascal.

    Existen tres funciones para  trabajar  con  la  cola.  `QUEUE'
    coloca  una  linea en la base de la cola. `PUSH' lo hace en la
    cima de la cola (pila). De esta manera, los datos introducidos
    mediante  QUEUE  despues  son  leidos en el mismo orden en que
    fueron  escritos  (FIFO),  mientras  que  los  datos  escritos
    mediante `PUSH' son leidos en orden inverso (LIFO). Existe una
    unica  instruccion  para  leer,  la  orden  `PULL'.  Vease  el
    siguiente ejemplo:

    /* uso de la cola en REXX */
    queue "uno"
    queue "dos"
    pull a
    pull b
    say a b
    push "uno"
    push "dos"
    pull a
    pull b
    say a b
    push "uno"
    queue "dos"
    pull a
    pull b
    say a b

    9.- Funciones de cadena

    Como  era  de  esperar en un lenguaje orientado a cadenas, las
    funciones para el tratamiento de estas son numerosas y de alto
    nivel.  Pueden agruparse en varias categorias, y en este punto
    voy a limitarme a  nombrarlas  y  describir  su  funcion,  sin
    entrar en detalles.

    9.1.- Funciones para extraer subcadenas de una cadena

    - SUBSTR()     extrae una subcadena de una cadena a partir de una
                   posicion determinada
    - LEFT()       extrae una subcadena por la izquierda
    - RIGHT()      extrae una subcadena por la derecha
    - WORD()       extrae una palabra de una cadena, identificada por
                   su numero
    - SUBWORD()    extrae una subcadena a  partir de una  determinada
                   palabra

    9.2.- Funciones de edicion

    - INSERT()     inserta una subcadena en una cadena
    - OVERLAY()    sobreescribe parte de una cadena con otra cadena
    - REVERSE()    invierte el orden de caracteres de una cadena
    - COPIES()     replica una cadena un numero determinado de veces

    9.3.- Funciones para borrar

    - DELSTR()     elimina una subcadena del interior de una cadena
    - DELWORD()    elimina una palabra dada

    9.4.- Funciones para formateo de lineas

    - SPACE()      a#ade o elimina espacios en blanco
    - CENTER()     centra  una  cadena a una  anchura  determinada,
                   a#adiendo espacios en blanco al principio, si es
                   preciso
    - STRIP()      elimina espacios en blanco al principio, final ,
                   o ambos, en una cadena

    9.5.- Funciones para contar

    - LENGTH()     cuenta el numero de caracteres de la cadena
    - WORDS()      cuenta el numero de palabras de la cadena
    - WORDLENGTH()

    9.6.- Funciones para comparar

    - VERIFY()     dadas dos cadenas, determina  el primer caracter
                   en que coinciden o difieren
    - COMPARE()    determina si dos cadenas son identicas
    - ABBREV()     devuelve  1  si  una  cadena  coincide  con  los
                   primeros caracteres de otra

    9.7.- Funciones para buscar posiciones

    - POS()        explora una cadena buscando el lugar de comienzo
                   de una subcadena
    - LASTPOS()    igual que  la anterior,  pero comienza  desde el
                   final, hacia atras
    - WORDINDEX()  determina la posicion del primer caracter de una
                   palabra dada

    10.- REXX y su comunicacion con el entorno

    10.1.- Entrada desde teclado

    Normalmente,  en la ejecucion de un programa interactivo, este
    pide al  usuario  que  introduzca  datos  desde  teclado.  Los
    programadores  en otros lenguajes saben lo engorroso que puede
    llegar a ser la depuracion de estos datos de  entrada,  y  por
    eso  REXX  ofrece  facilidades  ineditas.  Asi,  una cadena de
    entrada puede dividirse  automaticamente  en  sus  componente,
    asignando  cada palabra a una variable. Ademas, puede hacer un
    pre-proceso de la cadena tecleada, pasandola  a  mayusculas  o
    minusculas  antes de efectuar ninguna otra operacion. Ya hemos
    visto en algunos programas la instruccion `PULL'. En realidad,
    esta  instruccion  es  una  abreviatura  de  `PARSE  PULL', la
    instruccion que lee una cadena  introducida  por  el  usuario.
    Esta  cadena  puede pasarse a mayusculas o minusculas mediante
    las variantes `PARSE UPPER' y  `PARSE  LOWER'.   En  su  forma
    normal,  `PARSE  PULL',  la  cadena  no  sufre modificaciones.
    Veamos un ejemplo:

    /* uso de parse */
    say "introduce tu nombre"
    parse pull nombre
    if nombre == "Javier" then
        say "Hola Javier"
    else
        say "A ti no te conozco"

    Si el usuario teclea "javier"  o  "JAvier"  o  cualquier  cosa
    distinta  de  "Javier",  el  programa  declarara no conocer al
    usuario.  Cambiemos `parse pull'  por  `parse  upper  pull'  y
    cualquer  cosa que escriba el usuario sera pasada a mayusculas
    antes de hacer nada con ella, facilitando la identificacion de
    la cadena:

    /* uso de parse */
    say "introduce tu nombre"
    parse upper pull nombre
    if nombre == "JAVIER" then
        say "Hola Javier"
    else
        say "A ti no te conozco"

    Pero,  al  leer  una  cadena  introducida  por el usuario, las
    subcadenas  que  la  constituyen  pueden  individualizarse   y
    asignarse a variables. Vease el siguiente ejemplo:

    /* uso de parse */
    say "introduce tres o mas nombres"
    parse pull primero segundo tercero resto
    say primero segundo tercero "/" resto

    Cada  palabra va a una variable, y la ultima variable contiene
    lo que reste de la cadena en ese momento. Como  hariamos  para
    comprobar que se han introducido exactamente tres nombres? :

    /* uso de parse */
    correcto=0
    say "introduce exactamente tres nombres: "
    pull uno dos tres resto
    select
        when tres="" then say "faltan nombres"
        when resto="" then say "sobran nombres"
        otherwise correcto=1
    end

    Otra  util variante de `PARSE' es `PARSE VAR', que en lugar de
    tomar  su  entrada  del  teclado,  lo  hace  de  una  variable
    previamente asignada. Por ejemplo:

    /* uso de parse var */
    frase="los cretinos son soltados al mundo en carretones"
    parse var frase a b c d
    say a b c d  /* los cretinos son soltados */


    10.2.- Argumentos en linea de comandos

    Un  programa REXX puede ser llamado con argumentos en linea de
    comandos,  y  existe  una  forma  facil  de   asignar   dichos
    argumentos  a  variables  del  programa. De esto se encarga la
    instruccion `ARG'.  Por ejemplo:

    /* lectura de argumentos */
    arg primero segundo
    say primero segundo

    Si este programa es salvado con el nombre a.r y se llama  como
    "a.r   nuboso   soleado",   la  salida  imprimira  "nuboso"  y
    "soleado".

    Solo hemos hablado del  tratamiento  de  texto,  pero  no  hay
    ninguna   diferencia   cuando  se  trata  de  numeros.  Tomado
    literalmente del manual de IBM: "La forma en que  REXX  maneja
    los  numeros  es  otra  muestra  de  su  flexibilidad.  Muchos
    lenguajes de computadora tienen reglas especificas  para  leer
    texto  y numeros. En REXX, un numero es simplemente una cadena
    que puede ser calculada".

    10.3.- Comunicacion entre programas

    Un programa REXX puede llamar a  otros  programas  REXX,  o  a
    programas  escritos en cualquier lenguaje compilado. Existe un
    protocolo de comunicacion que funciona en los dos sentidos,  y
    que permite extender aun mas la potencia de REXX. Sin embargo,
    este  tema  me  parece  demasiado  especifico  para  un  tutor
    introductorio como este, y por otra parte solo dispongo de las
    especificaciones para DOS,  muy  detalladas  por  cierto,  que
    vienen con la ayuda en linea de REXX para el Sistema Operativo
    IBM PC-DOS 7.0, por lo que remito alli a los interesados.

    10.4.- REXX como lenguaje para elaborar guiones

    Una de las habilidades de REXX consiste en pasar  comandos  al
    Sistema  Operativo para que este los ejecute, y en capturar el
    valor  que  este  devuelva.  Puede  aprovecharse  entonces  el
    caracter  estructurado  de  REXX  y  su  potencia a la hora de
    procesar cadenas para crear guiones ( .BAT o .CMD en DOS ) muy
    elaborados, dificiles o imposibles de escribir de otro modo.

    11.- Funciones incluidas

    Son  muchas  las funciones incluidas en REXX, y muchas mas las
    que se ofrecen como programas REXX independientes junto con el
    interprete,  de  manera  que  puedan  ser  invocadas desde los
    programas. En cuanto a las segundas, dependen claro esta de la
    implementacion  de que dispongamos, y pueden considerarse como
    un obsequio.

    Respecto a las primeras, hemos citado muchas  a  lo  largo  de
    este  tutor,  pero  aun quedan mas. Por ejemplo, las funciones
    para operaciones  logicas  bit  a  bit:  BITAND(),  BITOR()  y
    BITXOR().  Toman dos argumentos, y hacen exactamente lo que su
    nombre sugiere.

    Tambien tenemos un conjunto de funciones de  conversion,  como
    B2X(),  para  pasar de binario a hexadecimal, C2D() para pasar
    de  caracter  a  decimal,  C2X()  para  pasar  de  caracter  a
    hexadecimal,  D2C()  de decimal a caracter, D2X() de decimal a
    hexadecimal,  X2B()  de  hexadecimal  a  binario,   X2C()   de
    hexadecimal a caracter y X2D() de hexadecimal a decimal.

    Algunas  funciones  matematicas  miscelaneas  como SIGN(), que
    devuelve el signo de un numero o expresion, ABS() que devuelve
    el  valor  absoluto  de  un  numero y RANDOM() que devuelve un
    numero  pseudo-aleatorio  entre   dos   limites   que   pueden
    definirse.

    12.- REXX como lenguaje dinamico

    Una  interesante  caracteristica  de  REXX  es que un programa
    escrito en este lenguaje  puede  modificarse  a  si  mismo  en
    tiempo   de   ejecucion,   y   llevarse   a  cabo  las  nuevas
    instrucciones. He aqui un ejemplo:

    /* uso de la orden interpret */
    dato = 'do 3; say "Hola a todos"'
    interpret dato

    Este programa crea una cadena, y  a  continuacion  la  ejecuta
    como si fuese una linea mas del programa. Otro:

    /* uso de la orden interpret */
    dato = altura
    interpret altura '=180'

    En  este  caso  se crea la variable `altura' y se le asigna el
    valor 180.

    13.- Para terminar

    Este tutor dista mucho de ser exhaustivo. Nisiquiera completo.
    Pero  espero  que  te haya servido para dar los primeros pasos
    con REXX, y te anime a mirar por la RED  otros  tutores  (  de
    momento  solo  en  ingles  ),  y  a escribir programas en este
    lenguaje, experimentando sus muchas  ventajas:  aritmetica  de
    precision   arbitraria   e  independiente  de  la  plataforma,
    estructuras de  control  flexibles,  tratamiento  avanzado  de
    cadenas, caracter multiplataforma, integracion con su entorno,
    elegante  tratamiento  de  arrays  y  estructuras   de   datos
    definidas por el usuario, etc.

    Si te interesa lo suficiente, te recomiendo dos adquisiciones.
    El manual "REXX User Guide  and  Reference",  de  IBM,  es  un
    librito asequible y completo al mismo tiempo, con mucho codigo
    de ejemplo, y con capitulos estructurados en dos partes, parte
    basica  y parte avanzada. La segunda adquisicion es el sistema
    operativo PC-DOS 7.0. Aparte de ser el mejor  DOS  que  se  ha
    hecho,  tiene  un  interprete REXX muy bueno y un completisimo
    sistema  de  ayuda  sobre  el  lenguaje.  Solo  este  elemento
    justifica el precio del sistema.  Pero ademas tienes un editor
    fantastico ( el famoso `E' ) y muchas y buenas  sorpresas  que
    no encontraras en MS-DOS. Vale la pena.

    Para terminar, siento la necesidad de declararme entusiasta de
    REXX, aunque no un  experto  y  mucho  menos  un  `guru'.   He
    programado en media docena de lenguajes y, como dije antes, he
    escrito tres o cuatro peque#os lenguajes, y la  sensacion  que
    empezaba  a  tener es que todos se parecian. REXX es distinto.
    Contiene media docena de ideas que lo hacen interesante  y  al
    mismo  tiempo  no es un lenguaje exigente en el sentido en que
    puedan serlo otros lenguajes `distintos', como LISP.

    Como soy el unico responsable de los errores u  omisiones  que
    puedas encontrar aqui, te ruego que me lo hagas saber a traves
    de mi direccion: gil@disc.ua.es. De esta forma, este tutor ira
    mejorando  con  el tiempo, y los que hablamos espa#ol podremos
    tener una referencia decente sobre nuestro lenguaje  favorito.

    14.- Traza de este documento

    Primera version en Enero de 2001.

    -------- Continuara! ---------