EL LENGUAJE REXX

    1.- Introducción.
    =================

    REXX  es  un lenguaje de programación con unas características
    sumamente interesantes. Desgraciadamente, y a pesar  de  haber
    cumplido  ya los veinte años, no es tan conocido como debiera,
    aunque esto se está remediando rápidamente, con una  comunidad
    de programadores activa y creciente. Esta pequeña introducción
    quiere también colaborar en este sentido, ya que, al  parecer,
    no  existen cursos de  REXX  en español, ni  documentación  de
    otro tipo, salvo el sistema de ayuda incluido con los Sistemas
    Operativos de IBM.

    ¿Qué es lo que lo hace atractivo? Varias  cosas. A lo largo de
    los años, he desarrollado tres o cuatro pequeños lenguajes  de
    programación,  y  he adquirido la certeza de que la existencia
    de tipos primitivos de datos es un error.  Un  programador  no
    debería 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 debería de pensar únicamente en términos
    de *números* y *cadenas de caracteres*.  Pero los  humanos  no
    tenemos  una  idea  intuitiva  de  los números, 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  también  representan
    números.  En definitiva, un lenguaje de programación avanzado,
    cercano a la forma en que pensamos  los  humanos,  debería  de
    tratar    únicamente   con   cadenas   de   caracteres.   Unas
    representarán palabras o frases, otras representarán  números.
    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 números, la aritmética es totalmente independiente
    de la plataforma. No sólo eso, es configurable por el usuario.
    Se acabaron los mensajes "underflow" u "overflow". Se acabaron
    las  representaciones  aproximadas  de números 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
    ahorrarán 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"  más potente y fácil de usar.
    Puede pasarse información de un programa REXX a otro  y  entre
    programas  REXX  y  programas  escritos  en otros lenguajes de
    programación, como C o Java.

    Quinto: el tratamiento que hace REXX de los arrays es original
    y  sumamente  elegante.  Tanto es así, que arrays de cualquier
    dimensión 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
    instrucciónes de  control  de  flujo  algo  más  elaboradas  y
    flexibles de lo que podemos encontrar en otros lenguajes.

    Quizás   sea  también  preciso  decir  que  REXX  no  contiene
    punteros. Un alivio para los primerizos y una pérdida para los
    programadores  más  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  más fácil de aprender que
    Java.  Escribe un programa en REXX y ejecútalo  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  extensión  de  REXX  que introduce
    objetos, Object REXX, y otra  extensión  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 gráfico, 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 gráficas.

    Por supuesto, también tiene algunas desventajas. La principal,
    es que de momento no existen compiladores gratuitos para REXX.
    Sin  embargo,  los intérpretes que existen funcionan muy bien,
    pudiendo procesar, en un Pentium 500,  del  orden  de  600.000
    cláusulas por segundo. Juzga tú mismo.

    2.- Para aprender REXX.
    =======================
    
    Bueno,  lo  primero  que  necesitas  es un intérprete REXX. En
    algunas distribuciones Linux puedes encontrar el intérprete de
    Regina, y si miras en la dirección:

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

    encontrarás  una  lista  de enlaces a implementaciones de REXX
    gratuitas. BREXX, por ejemplo, está bien, así que aquí  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
    algún  otro  lenguaje  de programación, de manera que haré una
    exposición inversa a la habitual, es decir, de arriba a abajo.
    Así  que, a medida que vayas leyendo esta introducción, teclea
    los pequeños programas y ejecútalos.  Es la forma más  rápida.
    Por  cierto,  que  me voy a limitar a la implementación que la
    comunidad REXX llama `clásica'. No hablaré 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  más  completa de información,
    aparte algunos excelentes documentos que pueden conseguirse en 
    la RED, es el  manual de  PC-DOS REXX, de IBM.  Es posible por 
    tanto que  algún punto  de los  explicados en lo que sigue sea 
    específico  de  esta  implementación. Si  es  así, mira  en la 
    documentación que acompañará a tu intérprete.

    3.- Estructura de un programa.
    ==============================

    Como  he  dicho antes, voy a hacer una exposición inversa a lo
    habitual, empezando por las estructuras mayores y descendiendo
    a  las  más pequeñas. Pero como comprendo tu impaciencia, aquí
    va el programa con que se comienza la exposición de  cualquier
    lenguaje desde hace 30 años:

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

    Edítalo,  sálvalo  con  el  nombre  1.r  u  otro  cualquiera y
    ejecútalo llamando al intérprete. Si éste se  llamase  `rexx',
    escribe en tu línea de comandos `rexx 1.r'.

    Un  programa  REXX comienza con un comentario y termina con la
    última línea del archivo fuente, o la instrucción `EXIT':

    /* patron de un programa */
      instrucciónes
    exit

    Dentro del bloque principal del programa puede haber  llamadas
    a subrutinas. Una subrutina es un segmento de código que puede
    ser llamado desde más de un lugar en  el  programa  principal.
    Este fragmento de código puede residir en el mismo archivo que
    el programa principal o en un archivo de disco  distinto.  Una
    subrutina  se  invoca  mediante  la  instrucción  `CALL',  por
    ejemplo:

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

    Opcionalmente, después de RETURN puede ir  una  expresión.  Si
    éste  es  el  caso,  la  expresión  es  evaluada,  y  el valor
    resultante asignado a una variable  especial  llamada  RESULT,
    que  puede ser usada en el programa principal. Si la expresión
    no aparece, el valor que pudiese  tener  previamente  asignado
    RESULT  es  eliminado,  de  manera  que  su evaluación 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 mayúscula, antes de entregarlos a la  rutina,
    mientras que la segunda los pasa exactamente. Bien, es la hora
    de un ejemplo:

    /* programa principal               */
    /* calculo del área del triángulo   */
    say "introduce la altura del triángulo: "
    pull altura
    say "introduce la base del triángulo: "
    pull base
    call area base,altura
    say "el area del triángulo 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
    función  ARG(). En esta modalidad, se llama a la subrutina con
    CALL, y  los  argumentos  pueden  ser  expresiones  que  serán
    computadas con ARG(). Por ejemplo:

    /* ejemplo del uso de ARG() */
    say "introduce en número: "
    pull a
    say "introduce un segundo número: "
    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 `función' además del concepto de
    subrutina.  A las funciones se las llama por  su  nombre,  con
    los argumentos entre paréntesis, y *deben* devolver siempre un
    valor mediante RETURN, aunque sea la  cadena  nula.  El  valor
    devuelto  es  usado  para  evaluar  la  expresión de la que la
    llamada a la función forme parte. Por ejemplo:

    /* ejemplo del uso de funciones en REXX */
    say "introduce un número: "
    pull a
    say "introduce otro número: "
    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 aquí, la estructura al  más  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 reflexión. REXX sólo 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 común, 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  podría  llevar  a  errores de difícil localización, sobre
    todo en programas largos.  Esencialmente, el problema consiste
    en  que  todas  las variables son globales. Para remediar esta
    situación, REXX ofrece la posibilidad de ocultar las variables
    de  subrutinas  al  resto  del programa, haciéndolas por tanto
    locales.  Si  se  desea  hacer  esto,  bastará  con   escribir
    `PROCEDURE'  al  inicio de la subrutina. Esta instrucción sólo
    puede llamarse una vez, y debe ser la primera  instrucción  de
    la  rutina.  Una  variante  de  esta instrucción es `PROCEDURE
    EXPOSE', que permite compartir con el programa  principal  una
    porción  de las variables de la rutina. Su sintaxis es simple:
    a continuación 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  función  interesante  es  SYMBOL(),  que toma como único
    argumento un nombre de variable entre dobles comillas  (  para
    distinguirlo  de  su valor ). SYMBOL() devuelve `BAD', `LIT' o
    `VAR' según que el argumento no  sea  un  símbolo  válido,  el
    argumento sea un nombre válido de variable al que aún no se ha
    asignado ningún valor ( o una constante numérica )  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 iniciada 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
      instrucción 1
      instrucción 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
    según se den o no ciertas condiciones.

    4.1.- Estructuras repetitivas.
    ------------------------------

    Verás que REXX destaca sobre otros lenguajes en la riqueza  de
    las  estructuras  repetitivas, que hacen muy difícil encontrar
    un caso en que la formulación de un problema no siga el camino
    que  cualquiera  consideraría `natural'. Veamos cuales son las
    posibilidades:

    i)  Bloque  repetitivo  simple.  Es  el  caso  más   sencillo.
    Simplemente,  se  especifica  el  número 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 especificación del intervalo. Puede  de
    esta  forma  controlarse  el  incremento  que  experimenta  el
    contador en cada iteración:

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

    iv) Uso de contador, especificacion del intervalo y número  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) Repetición del bloque un número ilimitado de veces:

    do forever
        say "hola"
    end

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

    /* leer números mientras sean distintos de cuatro */
    número=0
    do while número = 4
        pull número
    end

    En esta construcción, la condición es comprobada al  principio
    del bucle.

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

    /* leer números hasta que se introduzca el 4 */
    número=0
    do until número = 4
        pull número
    end

    En esta construcción, la condición es comprobada al final  del
    bucle.  Esto  significa  que  el  bloque  de  instrucciones es
    ejecutado al menos una vez.

    viii) Variable de control  y  condición.  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.
    Ejecútalo y lo veras más claro. Otro ejemplo:

    /*
    Leer 10 números, pero salir si se introduce la cadena vacía
    */
    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  práctico.  Piensa  en  el  caso en que has de explorar una
    matriz tridimensional buscando que un elemento tenga un  valor
    determinado.   Necesitarás  tres  bucles  anidados,  y  cuando
    encuentres  el  elemento  que  buscas, ¿qué  haces  ?.  Puedes
    guardarte  los  indices de la matriz y el valor, esperar a que
    concluyan las iteraciones y después seguir  la  ejecución  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  más
    abiertamente de  la  instrucción  `goto',  como  Pascal  o  C,
    *tienen*  la  instrucción  `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' también puede usarse para colocar `trampas',  de
    manera  que el programa interrumpa su ejecución normal y salte
    a una  rutina  específica  cuando  se  produzca una  condición
    determinada.   La  sintaxis  es  `SIGNAL  ON condición', donde
    `condición' 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 éste
    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 condición de error consiste en que REXX encuentra
    un nombre que podría ser el nombre de una variable  pero  ésta
    en   realidad   no  existe.  Por  ejemplo,  este  error  puede
    producirse  al  teclear  incorrectamente  el  nombre  de   una
    variable,  y  podría ser muy difícil de localizar a no ser por
    esta facilidad de REXX.

    SYNTAX: se ejecuta la subrutina llamada  SYNTAX:  cuando  REXX
    encuentra un error sintáctico 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  instrucción  `CALL'.  Asi   `CALL   ON   NOTREADY'
    reiniciaría  el  programa si durante su ejecución se produjese
    un error de Entrada/Salida.

    Hay otras dos instrucciones útiles  en  según  que  casos.  La
    primera  es  `LEAVE',  y  permite  salir  inmediatamente de un
    bucle. Podría usarse `SIGNAL', pero mientras que ésta  provoca
    que  el  programa continúe ejecutándose en cualquier punto que
    se desee, `LEAVE' salta inmediatamente después del  final  del
    bucle  actual.  Además,  cuando  hay  más de un bucle anidado,
    puede especificarse de cual de ellos se desea salir. No  tiene
    por que ser el más interno. Por ejemplo, el siguiente programa
    imprime parejas (i,j), pero pasa a la `i' siguiente en  cuanto
    encuentra el valor j=3 y además 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 instrucción anterior es `ITERATE', que salta al
    principio del bucle. Así, el programa anterior podría  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  construcción  condicional  en  REXX es similar a la que se
    encuentra en BASIC o Pascal.  Por  ejemplo,  en  el  siguiente
    programa se pide la introducción de un número, y se ejecuta un
    bloque de instrucciones u otro según este número cumpla  o  no
    una condición:

    say "introduce un número: "
    pull a
    if a>0 then
        do
            say "el número es mayor que cero"
            say "adiós, hasta la próxima"
        end
    else
        do
            say "el número es menor o igual que cero"
            say "que pases buen día"
        end

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

    say "introduce un número: "
    pull a
    if a>0 then
        do
            say "el número es mayor que cero"
            say "adiós, hasta la próxima"
        end
    else
        nop

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

    select
        when condicion_0 then
        do
            instrucciones
        end
        when condicion_1 then
        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 código.  Aquí  va
    la lista completa de ellos:

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

    Todos ellos  pueden  negarse con el operador `', teniendo, por
    ejemplo: \= ( no igual ), \<  ( no  menor  que ),  etc.  Estos
    operadores  comparan  números  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
    comparación. Existe una versión `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 números. Por
    ejemplo, 2 y 2.0 no son la misma cadena,  pero  son  el  mismo
    número. Ejecuta para probar el siguiente fragmento de código:

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

    5.- Símbolos compuestos.
    ========================

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

    Un  símbolo compuesto es un símbolo que tiene una `raiz' y una
    o varias extensiones, separadas por un punto. A la comprensión
    por el ejemplo:

    /* programa REXX que inicia un array de 20 números */
    do i=1 to 20
        a.i=0
    end

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

    El  uso  de  símbolos  compuestos  para  formar  registros con
    información relacionada también es directo.

    /* ilustración del uso de símbolos 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.- Aritmética
    ===============

    6.1.- Aritmética básica con REXX
    ---------------------------------

    La aritmética es una de las partes más gratificantes de  REXX.
    No  hemos  de preocuparnos de los rangos en que se moverán las
    cantidades,  ni  de  si  llevarán  o  no  signo,  ni  de   los
    moldeadores  de  tipo,  ni  de  la  imposibilidad  de  obtener
    resultados exactos cuando los números  son  muy  grandes.  Por
    ejemplo, trata  de  hallar  factorial de 100 con C: no puedes.
    Con REXX sí, míralo:

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

    Ejecútalo  y  obtendrás  el  resultado:  9.33262137E+157.  Por
    defecto,  REXX  usa  nueve  cifras  significativas, pero puede
    cambiarse a cualquier otra cantidad. Por ejemplo, si necesitas
    doscientos  dígitos,  escribe  `numeric  digits  200' antes de
    comenzar los cálculos, y vuelve a ejecutar el programa:

    93326215443944152681699238856266700490715968264381621468592963
    89521759999322991560894146397615651828625369792082722375825118
    5210916864000000000000000000000000

    Esto es factorial de 100!. Por lo demas, REXX se comporta  con
    la  aritmética  como es de esperar. El siguiente programa toma
    tres números 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

    además  de  los  operadores   habituales   de   suma,   resta,
    multiplicación  y división, tenemos la exponenciación (**), la
    división entera (%) y el resto (//).

    6.2.- numeric digits
    --------------------
    
    Como se ha  dicho  antes,  REXX  trabaja  internamente  con  9
    dígitos  significativos. Pero esta cantidad puede alterarse en
    cualquier momento para satisfacer  necesidades  especiales  de
    cálculo.  Por ejemplo, en cálculos con números enteros grandes
    desearíamos representaciones exactas, sea cual sea  el  número
    de dígitos que se requieran.

    Naturalmente, usar un elevado número de dígitos significativos
    tiene su precio en tiempo de cálculo. A veces, este precio  no
    compensa  de la pérdida 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
    dígitos y con 18 dígitos:

    /* perdida de velocidad al aumentar número de dígitos */
    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 máquina, el primer bucle se ejecuta en 3.40 segundos, y
    el segundo en 4.50. Digamos que  la  función  `time'  tiene  8
    formatos  distintos,  y  que  nosotros  hemos usado las formas
    time('E')  y  time('R').  La  primera  pone   en   marcha   un
    cronómetro,   si   no  está  ya  corriendo,  o  da  el  tiempo
    transcurrido, en caso contrario. La segunda pone el cronómetro
    a  cero  y lo inicia. Por último, la función 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 precisión elegida, nunca
    obtendremos 1, sino algo como 0.999999999.  O  imaginemos  que
    obtenemos  dos números, procedentes de dos cálculos distintos.
    Uno  de  ellos  puede  ser  0.2324256  y  el  otro  0.2324356.
    Obviamente  no  son  iguales, ¿o si?. Depende de qué precisión
    usemos para las comparaciones. Dependiendo de  qué  aplicación
    estemos  ejecutando,  dos  números  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 éste usando `numeric digits' menos 1.  Por  defecto,
    vale  0,  lo  que  indica  que  no se descartará ningún dígito
    durante la comparación. Un valor distinto de cero  indica  que
    número  de  dígitos  serán  descartados cuando se realicen las
    comparaciones. Considérese 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 función format()
    -------------------------

    Aunque no es estrictamente aritmética, incluyo en  este  punto
    una  descripción de la función format(), que controla la forma
    en que se imprimirán en pantalla los números. En su forma  más
    simple,  format()  toma  tres  argumentos: el  número que será
    impreso, el número de espacios antes del punto  decimal  y  el
    número  de espacios después del punto decimal. Si el número de
    espacios antes del punto es insuficiente, se genera un  error.
    Si  el  número 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 número 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 numéricas en
    formas de columna, que son más fáciles de  leer.  Si  format()
    necesita  descartar  algunos  decimales, realizará un redondeo
    convencional, pero si lo que deseamos es truncar, usaremos  la
    función  trunc(),  donde  se  especifica  el  número de cifras
    decimales que  se  usarán.  La  diferencia  entre  format()  y
    trunc() se ve claramente en el siguiente fragmento de código:

    /* 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 líneas
    --------------------------

    Hay dos diferencias fundamentales entre REXX y otros lenguajes
    de programación, que  lo  hacen  muy  agradable  de  usar.  La
    primera, es que el intérprete lleva cuenta de qué archivos hay
    abiertos, y se ocupa de cerrarlos al terminar el  programa  si
    es  que  no  se  hace explícitamente. Por tanto, no es preciso
    abrir explícitamente 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  más
    que  un  byte específico. 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  función  encargada  de  leer  una  línea  de un archivo es
    `linein()', que toma tres argumentos: el nombre  del  archivo,
    la  línea  que va a leerse y el número de líneas que se desean
    obtener.  Si el archivo no esta abierto, la primera llamada  a
    linein() lo abre, y coloca a 1 la variable que lleva la cuenta
    de qué línea es la siguiente  que  se  va  a  leer.  Así,  una
    llamada  como  linein(archivo,1,0) abre  el archivo, inicia el
    contador de líneas a 1 *pero no lee ninguna línea*, de  ahí el
    valor  0 en  el tercer  parámetro. En  sucesivas  lecturas, la
    variable que indica la siguiente línea  a  leer  se  actualiza
    automáticamente,  de  manera  que  no  hay  que especificarla.
    Naturalmente, en cualquier  momento  puede  especificarse  qué
    línea  se  desea  leer. Si es la línea `n', basta con escribir
    linein(archivo,n,1). Como segundo ejemplo, veamos un  programa
    que  lee  las  líneas  de  este  archivo  y a continuación 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

    Obsérvese por otra parte que un error  de  lectura,  producido
    por  querer  leer  más  alla  de  los  límites del archivo, se
    traduce en que linein() devuelve la cadena nula.

    En cuanto a la escritura de archivos, se efectúa  mediante  la
    función  lineout(),  que  toma  como  parámetros el nombre del
    archivo, la cadena que se desea escribir  y  la  posición.  El
    valor devuelto es 0 si todo fue bien o 1, si hubo algún 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ñadirá  al
    archivo.  Téngase  en  cuenta  que  si  se especifica un lugar
    concreto para escribir la  cadena, y la  cadena que  ocupa ese
    lugar tiene una longitud  menor de la  que pensamos  escribir,
    los datos del archivo  pueden corromperse. En   entornos  DOS,
    lineout()  añade  un carácter  nueva línea  y  un  carácter de 
    retorno de  carro  al  final  de  cada  línea.  El   siguiente  
    ejemplo  lee  este  archivo  y  lo escribe en orden inverso en
    el archivo llamado "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)
    
    Obsérvese 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  observará  también  que  se  ha
    llamada  como  subrutina,  para  disponer  del resultado de la
    operación en la variable RESULT y decidir si abandonar o no el
    bucle. Igualmente se podría haber escrito:

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

    Finalmente,  obsérvese  cómo  se  ha cerrado explícitamente 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 líneas, y son charin() y
    charout().    Además,   existen   algunas   otras    funciones
    misceláneas  que  pueden  ser  útiles.  Por  ejemplo,  lines()
    devuelve 1 si quedan líneas por leer de un  archivo,  y  0  en
    caso   contrario.   La   función   stream()   permite  obtener
    información sobre  un  archivo,  pero  su  descripción  merece
    espacio aparte.

    7.2.- La función stream()
    -------------------------

    La  sintaxis  de stream() es stream(nombre,operación,comando).
    `nombre' es el nombre del archivo. `operación' puede  ser  una
    de  las  letras `C', `S' o `D'. El primer caso indica que va a
    ejecutarse un comando sobre el archivo, y *sólo* 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 sólo lectura se puede añadir la palabra `READ', o
    `WRITE'  si  el archivo será de sólo escritura. `CLOSE' cierra
    el  archivo.  Finalmente,  `SEEK'   coloca   el   puntero   de
    lectura/escritura  en  la posición especificada. Esta posición
    puede contarse a partir de  la  posición  actual  (adelante  o
    atrás),  desde  el  inicio  del  archivo  o desde el final del
    archivo. Algunos ejemplos:

    stream(nombre,'C','seek =2')   /* puntero en la segunda línea */
    stream(nombre,'C','seek <2')   /* puntero en la segunda línea */
                                   /* contando desde el final     */
    stream(nombre,'C','seek +2')   /* puntero dos líneas adelante */
    stream(nombre,'C','seek -2')   /* puntero dos líneas 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 información 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  idéntico  a `S', salvo que se
    añade información adicional.  En  definitiva,  éstas  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 información puede apilarse sobre el último
    elemento de la pila o introducirse bajo el  primero.  De  esta
    forma,  esta  estructura  particular puede funcionar como pila
    LIFO y como pila FIFO simultáneamente. El tratamiento que hace
    REXX  de esta pila también es particular, aunque depende de la
    implementación.  En las implementaciones más sofisticadas,  la
    cola  puede persistir aun después 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  línea en la base de la cola. `PUSH' lo hace en la
    cima de la cola (pila). De esta manera, los datos introducidos
    mediante  QUEUE  después  son  leídos en el mismo orden en que
    fueron  escritos  (FIFO),  mientras  que  los  datos  escritos
    mediante `PUSH' son leídos en orden inverso (LIFO). Existe una
    única  instrucción  para  leer,  la  orden  `PULL'.  Véase  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 éstas son numerosas y de alto
    nivel.  Pueden agruparse en varias categorías, y en este punto
    voy a limitarme a  nombrarlas  y  describir  su  función,  sin
    entrar en detalles.

    9.1.- Funciones para extraer subcadenas de una cadena
    -----------------------------------------------------

    - SUBSTR()     extrae una subcadena de una cadena a partir de una
                   posición 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 número
    - SUBWORD()    extrae una subcadena a  partir de una  determinada
                   palabra

    9.2.- Funciones de edición
    --------------------------

    - 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 número 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 líneas
    ---------------------------------------

    - 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 número de caracteres de la cadena
    - WORDS()      cuenta el número de palabras de la cadena
    - WORDLENGTH()

    9.6.- Funciones para comparar
    -----------------------------

    - VERIFY()     dadas dos cadenas, determina  el primer carácter
                   en que coinciden o difieren
    - COMPARE()    determina si dos cadenas son idénticas
    - 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 atrás
    - WORDINDEX()  determina la posición del primer carácter de una
                   palabra dada



    10.- REXX y su comunicacion con el entorno.
    ===========================================

    10.1.- Entrada desde teclado
    ----------------------------

    Normalmente,  en la ejecución de un programa interactivo, éste
    pide al  usuario  que  introduzca  datos  desde  teclado.  Los
    programadores  en otros lenguajes saben lo engorroso que puede
    llegar a ser la depuración de estos datos de  entrada,  y  por
    eso  REXX  ofrece  facilidades  inéditas.  Así,  una cadena de
    entrada puede dividirse  automáticamente  en  sus componentes,
    asignando  cada palabra a una variable. Además, puede hacer un
    pre-proceso de la cadena tecleada, pasándola  a  mayúsculas  o
    minúsculas  antes de efectuar ninguna otra operación. Ya hemos
    visto en algunos programas la instrucción `PULL'. En realidad,
    esta  instrucción  es  una  abreviatura  de  `PARSE  PULL', la
    instrucción que lee una cadena  introducida  por  el  usuario.
    Esta  cadena  puede pasarse a mayúsculas o minúsculas 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  declarará no conocer al
    usuario.  Cambiemos `parse pull'  por  `parse  upper  pull'  y
    cualquer  cosa que escriba el usuario será pasada a mayúsculas
    antes de hacer nada con ella, facilitando la identificación 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"

    Como digo, al leer una cadena introducida  por el usuario, las
    subcadenas  que  la  constituyen  pueden  individualizarse   y
    asignarse a variables. Véase el siguiente ejemplo:

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

    Cada  palabra va a una variable, y la última variable contiene
    lo que reste de la cadena en ese momento. ¿Cómo haríamos  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  útil 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 línea de comandos
    --------------------------------------

    Un  programa REXX puede ser llamado con argumentos en línea de
    comandos,  y  existe  una  forma  fácil  de   asignar   dichos
    argumentos  a  variables  del  programa. De esto se encarga la
    instrucción `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  imprimirá  "nuboso"  y
    "soleado".

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

    10.3.- Comunicación entre programas
    -----------------------------------

    Un programa REXX puede llamar a  otros  programas  REXX,  o  a
    programas  escritos en cualquier lenguaje compilado. Existe un
    protocolo de comunicación que funciona en los dos sentidos,  y
    que permite extender aun más la potencia de REXX. Sin embargo,
    este  tema  me  parece  demasiado específico para un documento
    introductorio como éste, y por otra parte sólo dispongo de las
    especificaciones para DOS,  muy  detalladas  por  cierto,  que
    vienen con la ayuda en línea de REXX para el Sistema Operativo
    IBM PC-DOS 7.0, por lo que remito allí a los interesados.

    10.4.- REXX como lenguaje para elaborar guiones
    -----------------------------------------------

    Una de las habilidades de REXX consiste  en pasar  órdenes  al
    Sistema  Operativo para que éste las ejecute, y en capturar el
    valor  que  éste  devuelva.  Puede  aprovecharse  entonces  el
    carácter  estructurado  de  REXX  y  su  potencia a la hora de
    procesar cadenas para crear guiones ( .BAT o .CMD en DOS ) muy
    elaborados, difíciles o imposibles de escribir de otro modo.

    11.- Funciones incluidas.
    =========================

    Son  muchas  las funciones incluidas en REXX, y muchas más las
    que se ofrecen como programas REXX independientes junto con el
    intérprete,  de  manera  que  puedan  ser  invocadas desde los
    programas. En cuanto a las segundas, dependen claro está de la
    implementación  de que dispongamos, y pueden considerarse como
    un obsequio.

    Respecto a las primeras, hemos  citado  muchas a  lo  largo de 
    este   documento,  pero  aún   quedan  más.  Por  ejemplo, las 
    funciones  para  operaciones  lógicas  bit  a  bit:  BITAND(),
    BITOR() y BITXOR().  Toman dos argumentos, y hacen exactamente
    lo que su nombre sugiere.

    También tenemos un conjunto de funciones de  conversión,  como
    B2X(),  para  pasar de binario a hexadecimal, C2D() para pasar
    de  carácter  a  decimal,  C2X()  para  pasar  de  carácter  a
    hexadecimal,  D2C()  de decimal a carácter, D2X() de decimal a
    hexadecimal,  X2B()  de  hexadecimal  a  binario,   X2C()   de
    hexadecimal a carácter y X2D() de hexadecimal a decimal.

    Algunas  funciones  matemáticas  misceláneas  como SIGN(), que
    devuelve el signo de un número o expresión, ABS() que devuelve
    el  valor  absoluto  de  un  número y RANDOM() que devuelve un
    número  pseudo-aleatorio  entre   dos   límites   que   pueden
    definirse.

    12.- REXX como lenguaje dinámico.
    =================================

    Una  interesante  característica  de  REXX  es que un programa
    escrito en este lenguaje  puede  modificarse  a  sí  mismo  en
    tiempo   de   ejecución,   y   llevarse   a  cabo  las  nuevas
    instrucciones. He aquí un ejemplo:

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

    Este programa crea una cadena, y  a  continuación  la  ejecuta
    como si fuese una línea más 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.
    ===================

    Esta introducción  está lejos  de ser  exhaustiva. Ni siquiera
    completa. Pero puede que te haya servido en tus primeros pasos
    con REXX, y te anime a mirar por la red más  documentación,  y  
    a escribir  programas en  este  lenguaje,  experimentando  sus 
    muchas  ventajas:  aritmética  de   precision   arbitraria   e  
    independiente   de   la   plataforma,  estructuras de  control  
    flexibles,  tratamiento  avanzado  de cadenas, integración 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 código
    de ejemplo, y con capítulos estructurados en dos partes, parte
    básica  y parte avanzada. La segunda adquisición es el sistema
    operativo PC-DOS 7.0. Aparte de ser el mejor  DOS  que  se  ha
    hecho,  tiene  un  intérprete REXX muy bueno y un completísimo
    sistema  de  ayuda  sobre  el  lenguaje.  Sólo  este  elemento
    justifica el precio del sistema.  Pero además tienes un editor
    fantastico ( el famoso `E' ) y muchas y buenas  sorpresas  que
    no encontraras en MS-DOS. Vale la pena.

    Como soy el único responsable de los errores u  omisiones  que
    puedas encontrar aquí, te ruego que me lo hagas saber a través
    de mi dirección: gil@disc.ua.es.

    

    Enero de 2001.