Category:Amazing Hopper

From Rosetta Code
(Redirected from Amazing Hopper)
Language
Amazing Hopper
This programming language may be used to instruct a computer to perform a task.
See Also:


Listed below are all of the tasks on Rosetta Code which have been solved using Amazing Hopper.
   ___       _____ ___   __           __  ___  ___  ___ ___
    |  |\  |   |   |  \ /  \    |  | /  \ |  \ |  \ |   |  \
    |  | \ |   |   |  | |  |    |  | |  | |  | |  | |   |  |
    |  |  \|   |   |__/ |  |    |__| |  | |__/ |__/ |_  |__/
   _|_ |   |   |   |  \ \__/    |  | \__/ |    |    |__ |  \


HOPPER (Amazing Grace!), es un prototipo de máquina virtual, inspirado en las ideas de Grace Murray Hopper, pero llevadas un paso más allá. Su sintaxis corresponde a un "pseudo-ensamblador", y pretende ser una "gramática profunda", es decir, una gramática que permita sostener a otras gramáticas, definidas en el nivel del programador final, denominado "nivel de abstracción superior".

Cosiga una versión actualizada en el sitio Web:

 https://github.com/DanielStuardo/Amazing-Hopper

Como prototipo, HOPPER es un programa cuya naturaleza responde a la investigación: ese es el motivo de su diseño y desarrollo.

Como prototipo, se ofrece gratuitamente, y en modo "AS IS": su autor no se hace responsable de ningún desastre ocurrido a instancias de la mala manipulación del código, o por el surgimiento de algún "bug" no detectado.

HOPPER se compone de dos programas:

1) "hopper", que realiza análisis y ejecución inmediata del script; también puede generar un archivo binario.

  Ejemplo:
      $ hopper src/holamundo.com         --- ejecuta el script
      $ hopper src/holamundo.com -x      --- genera una versión binaria del archivo
      

2) "bhopper", que ejecuta el archivo binario.

  Ejemplo:
      $ bhopper holamundo

BHOPPER usa menos heap que HOPPER, es más rápido en la carga, pero no hay mucha diferencia en cuanto al uso de la memoria, y no existe diferencia en la velocidad de ejecución.

HOPPER, como prototipo, hasta el momento ofrece:

  • ) un potente procesador de macros, que permite definir gramáticas, tanto formales, como de lenguaje natural.
  • ) distintas maneras de acceder a los datos del stack, tanto postfijo, como el "modo natural Hopper".
  • ) soporte básico de tipos de datos: numéricos, cadenas, y lógicos.
  • ) soporte para definir contextos, y desplazamientos lingüísticos, en gramáticas de alto nivel.
  • ) soporte para procesamiento de cadenas y matrices de cadenas.
  • ) soporte para cálculos matemáticos y trigonométricos.
  • ) soporte para cálculos estadísticos, operaciones de bits y cambio de base.
  • ) soporte para operaciones de fecha y hora.
  • ) soporte para trabajo matricial. Ejemplo: Si A y B son matrices, {A,B}mul, o {A} mulby (B), realiza multiplicación haddagard.
  • ) soporte para proceso de matrices.
  • ) soporte para captura y manejo de errores tipo TRY/CATCH.
  • ) soporte para manejo de sockets, mensajería System-V, y expresiones regulares.
  • ) soporte para operaciones recursivas.
  • ) soporte para manejo de archivos de texto, en formato cadena y matricial.
  • ) soporte para escribir código infijo, en modo "inline", vía macro-procesamiento.
  • ) soporte para simulación de funciones vía macro-procesamiento.
  • ) soporte para definición de estructuras de control, vía macro-procesamiento.
  • ) soporte para definir gramáticas de alto nivel, tanto formales como naturales, vía macro-procesamiento.
  • ) entre otros.

INSPIRACION

HOPPER es una aplicación práctica de una investigación realizada por el autor, sobre "gramáticas profundas" (al estilo "Wittgenstein", no teniendo nada que ver con el concepto elaborado por "Chomsky"), un soporte para la definición de cualquier forma o estilo de programación, ya sean lenguajes formales, como lenguajes naturales. Se trata, por lo tanto, de un prototipo que demuestra los resultados actuales de dicha investigación, y podría ser modificado en el futuro, gracias al avance de la misma.

En la década de los años 50 del siglo XX, Grace Murray Hopper, científica computacional estadounidense, buscaba la manera de escribir lenguajes de programación más cercanos al ser humano. En aquella época, los programadores debían programar en ensamblador. Los resultados de su esfuerzo, se traducen en un prototipo llamado FLOW-MATIC, y en el lenguaje COBOL, este último en uso en la actualidad.

El devenir histórico de los lenguajes de programación, ha demostrado que la visión de Hopper se ha perdido. Los lenguajes formales se vuelven cada vez más técnicos, se alejan de la compresión natural, y se convierten en monstruosos contenedores de una ingente cantidad de librerías y añadidos.

Lo anterior me llevó a pensar en la idea de un lenguaje de programación del tipo "universal", entendido no como una herramienta que soporte los paradigmas actuales de la programación, sino, como una herramienta "accesible" por todos los programadores, en cualquier idioma, y gusto por un estilo formal particular. En tal sentido, Hopper responde a la idea de "universalidad", dado que entrega el soporte para definir gramáticas formales y naturales, más cercanas al hablante, cumpliendo el propósito inicial de la asombrosa Grace. Asimismo, Hopper proporciona una sintaxis de un "pseudo-ensamblador" fácil de leer y comprender, al cual se le han añadido instrucciones que empaquetan procesos complejos, como las operaciones matriciales. No obstante, estas últimas características no deben ser tomadas como parte de un estándar, dado que Hopper es un prototipo.

Este "pseudo-ensamblador" cumple el propósito de soportar cualquier dialecto elaborado en un nivel de abstracción superior, y de proveer una base de traducción para dichos dialectos.

Un programador Hopper, debe conocer la gramática profunda, para elaborar dialectos a la medida. No obstante, no es un requisito dicho conocimiento, dado que un usuario podría aprender directamente el dialecto, y si este ha sido bien elaborado, podría actuar independiente de la gramática profunda. Esto define la existencia de dos tipos de programadores Hopper.

La presente ayuda, solo repasa los aspectos del pseudo-ensamblador. A modo de ejemplos, se mostrarán definiciones de instrucciones formales y en lenguaje natural.

DETALLES TECNICOS

Gran parte del código interno de la máquina virtual, incluida la estructura de datos, ha sido adaptado del código fuente de Harbour 3.0, y el crédito se respeta para cada uno de sus autores. Por lo mismo, el manejo de tipos de datos se suscribe a los límites de dicha estructura de datos.

El analizador sintáctico, y el motor de macro-procesamiento, están escritos en Harbour 3.0 y Ansi C, mientras que la máquina virtual está escrita en Ansi C.

Se distribuye junto a la suit XU (VM) y LAICA (editor de código).

Herramientas usadas en el desarrollo de HOPPER:

  • ) harbour 3.0.
  • ) gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0. (y 9.3.0, Ubuntu 20.04)
  • ) valgrind 3.13.0.

Dudas, consultas y aportes, escribir a daniel.stuardo@gmail.com

EJECUCION DE SCRIPT

Un script HOPPER debe ser ejecutado con las siguientes opciones:

  hopper <script> [parámetros-del-programa] [parámetros-del-script]

donde

  <script> es el script fuente, con extensión ".com", aunque, en la práctica, es posible usar cualquier extensión.
 
 

[parámetros-del-programa] puede ser:

  -p          genera archivo preprocesado (.ppo). Se puede ejecutar.
  -l          genera archivo librería (.lib), usado por la cláusula
              '#import'. Si en el archivo librería existen prototipos,
              es necesario declararlos en el archivo principal.
  -ne         no ejecuta.
  -x          genera un archivo binario, ejecutado por BHOPPER.
              El archivo se genera sin extensión.
  -o <name>   especifica un nombre de archivo para salida binaria.
              Si no se especifica, la salida será el nombre del script,
              sin extensión, en el directorio actual.
  -lb         ejecuta un archivo binario con HOPPER.
  -w          desactiva mensajes de warning.
  -d          imprime el argumento de 'return' en el 'stdout'.

[parámetros-del-script] son los parámetros del script.


ARCHIVO DE PREPROCESAMIENTO

Un archivo de este tipo, con extensión ".PPO", no es otro que un archivo en su forma de la "gramática profunda", el pseudo-ensamblador, que es directamente traducible al código de la máquina virtual de HOPPER. Cualquiera de las siguientes líneas hacen lo mismo:

  hopper script.com
  

o bien

  hopper script.com -p     // esto genera script.com.ppo
  hopper script.com.ppo

Si el programador escribe su script sin usar macros, no es necesario el uso de la opción "-p". La opción "-p" es útil para conocer la forma real de la aplicación de una macro, en el caso de crearse macros nuevas.

HOPPER, en la práctica, puede ser tomado como un lenguaje de macros, si solo se usa en el ámbito de los lenguajes de alto nivel.

    • EJEMPLOS **

Ejemplo de análisis de un script, con generación de archivo de preproceso, pero sin ejecución:

  hopper script.com -p -ne

Ejemplo de ejecución de un script, desactivando mensajes de "warning" (no recomendado):

  hopper script.com -w

Ejemplo de ejecución de un script, generando un archivo de preproceso:

  hopper script.com -p

Ejemplo de Creación de un archivo binario, con salida guardada en directorio "bin":

  hopper script.com -ne -x -o bin/script

Para ejecutar un archivo binario, puede hacerlo así:

  hopper <archivo-bin> -lb [parámetro-del-programa] [parámetros-del-archivo-bin]

o

  bhopper <archivo-bin> [parámetro-del-programa] [parámetros-del-archivo-bin]

donde

  <archivo-bin> es el archivo obtenido con la opción "hopper script -x [-o archivo-bin]".

donde [parámetro-del-programa] es:

  -d          HOPPER imprime el argumento de 'return' en el stdout.

Ejemplo de creación de librerías.

  hopper script.com -l

Esto creará un archivo llamado "script.com.lib", que contendrá solo la versión preprocesada de los bloques de código escritos luego de la cláusula .LOCALS. Dicho archivo podrá ser importado a cualquier código, con la directiva #IMPORT (Ver DIRECTIVAS DEL PREPROCESADOR para mayor información).


QUIEBRE DE EJECUCION DE UN PROGRAMA

Un programa HOPPER en ejecución no podrá ser abortado. Tampoco se recomienda eliminar el proceso con KILL. Para abortar una ejecución, debe añadirse al script cualquiera de las instrucciones:

  kbhit?, kbesc?, y kbctrlc?
  

que capturan la pulsación de teclas, devuelven un valor de verdad TRUE cuando eso ocurre.

Ejemplo:

  i=0
  loop infinito:
      {"Infinito\n"}print
      kbesc? jt(SALIR)
      jmp(loop infinito)
  SALIR:
  

Hay que tener mucho cuidado con esto, dado que es fácil caer en un loop infinito.

Estas instrucciones solo son efectivas dentro de un loop.

Para quebrar la ejecución de un programa, sin tener la necesidad de usar las instrucciones anteriores, se puede añadir, luego del MAIN:, al principio del programa:

  .ctrlc

esto habilitará CTRL+C para terminar la ejecución en cualquier parte de ésta.


HOLA MUNDO!

Ejemplo 1:

 main:
    {"Hola mundo!\n"}print
    {0}return

ejecutar con:

    hopper holamundo.com

Ejemplo 2:

 main:
    {"Hola mundo!\n"}return
 

ejecutar con:

    hopper holamundo.com -d

La etiqueta MAIN: es obligatoria y única, y cualquier cosa que se declare, debe ser declarada a continuación de esta etiqueta. La instrucción RETURN es obligatoria, pero pueden haber varias ocurrencias dentro de un script.

APILAMIENTO Y DESAPILAMIENTO DE DATOS: EL STACK, LA MEMORIA DE HOPPER

El stack almacena datos que serán los argumentos de las instrucciones HOPPER.

 {DATO}  ==> mete DATO al stack.

donde DATO puede ser:

 1) Una constante numérica, de cadena, un parámetro.
 2) Un array.

OBSERVACION. Pueden apilarse muchos datos en el stack, aunque el número de posiciones del stack es limitado (10), este puede ser cambiado con la cláusula .STACK. Ejemplo:

  .stack 20
  

Define un stack de trabajo de 20 posiciones.

Además, es necesario que dicha cláusula sea puesta dentro del contexto MAIN.

APILAR DATOS

Las instrucciones de HOPPER, usualmente, hacen uso de argumentos. Estos argumentos deben ser apilados en el STACK DE TRABAJO. La forma de apilamiento de datos en el stack, se hace de dos maneras:

1) la manera DIRECTA.

  Ejemplo: {2,"mensaje",0.001}
           => mete el número 2           : este es el HEAD de la pila
           => mete la cadena "mensaje"
           => mete el número 0.001       : este es el TOP de la pila

2) la manera INDIRECTA, a través de los resultados devueltos por el trabajo de las instrucciones.

  Ejemplo: {2,3}mul
           => mete 2
           => mete 3
           "mul" saca 3, saca 2, multiplica: 3*2=6
           "6" es el nuevo dato en el stack.

DESAPILAR DATOS

Los datos en el stack son argumentos de las instrucciones. Estos son sacados, de manera "natural", desde el TOP hasta el HEAD. Esto significa que ciertas instrucciones de cálculo no obedecen, de manera natural, a la forma de cálculo POSTFIJO.

Por ejemplo:

  {2,3}mul     => multiplica 3*2, o 2 multiplica a 3
  {2,3}add     => suma 3+2,       o 2 es sumado a 3
  {2,3}sub     => resta 3-2,      o 2 es restado a 3
  {2,3}div     => divide 3/2,     o 2 divide a 3
  etcétera.

CALCULO POSTFIJO

El cálculo POSTFIJO toma el dato anterior al TOP, y luego, lo opera con el dato en el TOP, o sea, al revés de lo que HOPPER hace de manera normal.

  POSTFIJO:     instruccion dato[TOP-1] op dato[TOP]
  HOPPER:       instrucción dato[TOP] op dato[TOP-1]

Para realizar cálculo POSTFIJO, se emplea un flag antes y después de la instrucción, o grupo de instrucciones que lo aceptan. Las instrucciones que aceptan el modo POSTFIJO, son las matemáticas.

Ejemplo:

  {2,3} postfix,sub,postfix
  postfix,{2,3},sub,postfix
  
  Ambos hacen lo mismo:  2-3 = -1

Otro ejemplo:

  postfix,{4,2,3}mul,{1}sub,mul,postfix
  
  El orden de cálculo es el siguiente: 2*3 = 6, 6-1 = 5, 4*5 = 20
  

Sin el flag POSTFIX:

  {4,2,3}mul,{1}sub,mul
  
  Se desarrolla así: 3*2 = 6, 1-6 = -5, -5*4 = -20

CALCULO MODO INFIJO

Debido a la inherente complejidad de los cálculos anteriores, se ha añadido una modalidad que usa POSTFIX, de manera interna. El cálculo infijo se debe colocar dentro de la macro #COMPUTE o #FX.

Ejemplo:

  #compute ( 4*(2*3-1) )
  
  Esto devolverá a la VM la siguiente línea:
  postfix,{4},{2},{3},mul,{1},sub,mul,postfix

Es posible usar de manera más completa la macro #COMPUTE, pero eso se describirá con más detalle, más adelante.

REPETICION DE APILADO

Si se desea apilar un mismo dato varias veces, se puede usar el control "*" antes del dato a apilar. Ejemplo:

  { *100}   ==> es idéntico a {100,100}
  {***10}   ==> es idéntico a {10,10,10,10}

OBSERVACION. el control "*" sirve para repetir una instrucción. Por ejemplo: *add ==> add,add.

MANTENER DATOS EN STACK

Algunas veces, se querrá usar un dato en el stack más de una vez, y no es posible usar el control "*". En tal caso, se usa el control "!" antecediendo a la instrucción a continuación. Ejemplo:

  {2} plus '3', {5}minus'1',!mul,div,{"\n\n"},print
  ==> esta línea resuelve la fórmula:
      (2+3) * (5-1) / (2+3)

La instrucción "!mul" resuelve "5-1" multiplicado por "2+3" (según el orden de HOPPER), y deja el resultado de "2+3" en su lugar, para luego ser consumido como denominador por "div".

Un control "!" mantiene un solo dato en el stack, partiendo desde el HEAD. Dos controles, "!!", mantienen los datos en la posición HEAD y HEAD+1 del stack. En general, El control "!(n)", mantiene "n" datos en el stack. Ejemplo:

 // imprime todo el stack, pero lo mantiene gracias a "!!!"
  {"Mensaje de Hopper: ",2000.9,"\n"}!!!print
 // imprime otra vez el stack, pero esta vez lo consume.
  {"\n"}print

imprime:

  Mensaje de Hopper: 2000.9
  Mensaje de Hopper: 2000.9

OBSERVACION. La instrucción PRINT consume el stack, desde el HEAD hasta el TOP. Más detalles, más adelante.

MOVER Y COPIAR DESDE EL STACK

HOPPER usa variables que pueden adquirir datos de diferentes tipos. Para usar una variable, se debe inicializar previamente. Ejemplo:

  v=0
  s="", s1="Hola mundo!\n"
  
  (Sobre el uso de variables, ver las secciones que hablan de ellas, a continuación)
** MOVER un dato del stack a una variable:   
  {100},mov(v)
  ==> ahora, el stack está vacío, y v=100
** COPIAR un dato del stack a una variable:
  {"mensaje"},cpy(s)
  ==> ahora, el stack continúa con "mensaje", pero s="mensaje"

OBSERVACION. Tanto MOV como CPY efectúan una copia dura del contenido.

Ejemplo:

  n=0
  {100,"mensaje"},len,mul,mov(n)
  ==> el stack queda vacío, y n=700
  
  n=0
  {100,"mensaje"},len,mul,cpy(n)
  ==> en el stack queda 700, y n=700

OBSERVACION. Si va a usar #COMPUTE, considere que solo se aplica MOV para "=". Ejemplo:

  #compute( n=len("mensaje")*100 )
  
  genera la siguiente línea preprocesada:
 
  postfix,{"mensaje"},len,{100},mul,postfix,n=0,mov(n)

OBSERVACION 2. Para aplicar CPY en #COMPUTE, debe usar ":=". Ejemplo:

  #compute{
     n:=( len("mensaje")*100 )
  }
  genera la siguiente línea:
  
     {"mensaje"},len,{100},mul,r=0,cpy(r)
 
  NOTA: si va a usar ":=", la expresión a asignar debe ser colocada entre paréntesis.

APILAR UNA MATRIZ EN STACK

HOPPER puede apilar una matriz o vector (un array), de dos maneras:

 1) copiando el puntero a la matriz (forma usual).
 2) copiando la matriz completa.

Para copiar la matriz completa, se debe usar el control "@" antes de la variable. Ejemplo:

  n=0, {2,3}rand array(n)  // crea un array aleatorio de 3 filas y 2 columnas en "n"
  t=0
  {@n},++n,{100},mul,mov(t)
  {n,"\n",t,"\n"},print
  
  CEIL es función techo. El código anterior despliega:
  1.94807 1.17598   ==> esto es "n" incrementado en 1.
  1.61907 1.90591
  1.3727 1.84048
 
  94.807 17.5982    ==> esto es "n" original, por 100 = t.
  61.9072 90.5911
  37.2703 84.048
 
  En cambio, si no se usa el control "@", solo copia el puntero.
  n=0, {2,3}rand array(n)  // crea un array aleatorio de 3 filas y 2 columnas en "n"
  t=0
  {n},++n,{100},mul,mov(t)
  {n,"\n",t,"\n"},print
  
  El código anterior despliega:
  1.57044 1.47523   ==> esto es "n" incrementado en 1.
  1.8034 1.84302
  1.93367 1.23961
 
  157.044 147.523   ==> esto es "n" incrementado, por 100 = t.
  180.34 184.302
  193.367 123.961
  

OBSERVACION. El control "@" solo puede ser usado con matrices. Los datos simples, son copiados en el stack en forma íntegra.

OBSERVACION 2. las instrucciones MOV y CPY crean una instancia de la matriz guardada en el stack. Ejemplo:

  {@n},mov(t)
  {n},mov(t)
  ==> ambos crean una copia de "n" en "t". "n" y "t" son independientes.

OBSERVACION 3. No se permiten instrucciones combinadas dentro de "{}". Observe que "++n" está fuera de "{}".

CUESTIONES SINTACTICAS TRIVIALES

COMENTARIOS

HOPPER soporta comentarios de línea:

  // este es un comentario de línea
  // este otro también

y comentarios de bloque:

  /* este es un comentario de bloque
     que puede ser empleado en cualquier
     parte */
  
  /* este otro también lo es */

SEPARADOR DE STRING

HOPPER usa la comilla como único separador de string. Por ejemplo:

 "Hola mundo" --> es un string válido.


USO DE APOSTROFE

HOPPER permite el uso de apóstrofes como alternativa a los paréntesis, pero no está permitido anidar apóstrofes. Por ejemplo:

 {"hola mundo"},len, mov 's'

OBSERVACION. Los apóstrofes no pueden ser usadas en el contexto #COMPUTE, pero sí en el contexto de macro-sustitución. La macro-sustitución #COMPUTE es un "convertidor" sintáctico, y no reconoce apóstrofes.

USO DE COMAS y SEMICOLON

Las instrucciones deben ser separadas por comas ",", o por semicolon ";", en las siguientes circunstancias:

1) cuando hay dos instrucciones seguidas.

  Ejemplo:
       {10,2,3}add,mul

2) para escribir instrucciones hacia el lado.

3) El uso de SEMICOLON es obligatorio en el caso de añadir argumentos compuestos en macros.

  Ejemplo:
       for ( i=1;j=10, {i} lethan '10', ++i; --j )
          ...
       next

La razón del punto 3, es que el procesador de MACROS determina un argumento hasta la ocurrencia de una coma ",". En el caso del ejemplo, el procesador de macros tomará como un único argumento "i=1;j=10", y "++i;--j", pero, el analizador de la VM de Hopper los considerará como dos instrucciones separadas.

4) Se usa SEMICOLON en contexto #COMPUTE, como TERMINADOR de una expresión de bifurcación inline.

  Por Ejemplo:
       #compute ( x+= (v==1) ? 1 : (v<0 && c==0) ? -1 : 0 ; )

OBSERVACION. La macro FOR es una de una familia de macros relacionadas, incluida dentro del archivo de definiciones STDIO.HH.


USO DE PUNTO

El punto "." tiene importancia en HOPPER: al final de un párrafo, determina si el stack queda con datos, o no. El punto se usa cuando se requiere detectar que el stack está vacío, al final de un párrafo, antes de comenzar con uno nuevo. En tal sentido, el "." es traducible a una línea de instrucciones de la "gramática profunda", y esto se puede ver si se obtiene la versión preprocesada del script.

Si un párrafo tiene un punto al final, y la ejecución llega hasta ese punto con datos en el stack, se levantará un error. Dicho error puede ser atrapado con una macro TRY/CATCH, sin embargo, no es recomendable, porque se delata un error estructural del programa.

Ejemplo de macro-sustitución de alto nivel:

  imprimir (la suma entre (pi elevado al cuadrado, y '10'), por '10').
  por otro lado,imprimir (la suma entre (la raíz de PI, y '10'), por (la suma entre (10, y '15'))).
  toma '10', más '2', dividido-e por '7'; hecho esto, respalda en 'r'
  si es igual a ( el número '1' más 'r'), realiza el proceso alternativo.

La línea 2 y 3 esperan comenzar con el stack vacío. La cuarta línea, espera que exista un valor en el stack (el resultado de "10+2\7"), para compararlo con la suma "1+r"; si tal comparación es verdadera, realiza el proceso alternativo.

El control "." es traducido en tiempo de preprocesamiento de macros, como:

  emptystack?,not,do{ {"you did not close the idea "},raiserror(1100)}

OBSERVACION. El ejemplo de alto nivel usa macro-procesamiento, procesamiento de contexto y desplazamiento lingüístico. Estos tópicos serán revisados más adelante, con mayor profundidad. Para este ejemplo, se han omitido las definiciones correspondientes.

OBSERVACION 2. Dicho ejemplo de alto nivel pudo haberse escrito en alemán, inglés, rapa-nui o chino. También, pudo emplearse una notación formal.


NUMEROS NEGATIVOS

HOPPER requiere que un número negativo sea escrito entre paréntesis, en el modo #COMPUTE. Por ejemplo:

 #compute ( x = x*(-2) )
 

Esto no ocurre cuando se escribe el script en la forma de la gramática profunda. La razón es sencilla: cuando el analizador sintáctico del procesador #COMPUTE encuentra un número negativo, lo convierte a una resta:

  (-4)    --> (0-4)
  (-tasa) --> (0-tasa)

Y para hacer esto, necesita encontrar el token "(-". Si no lo encuentra, puede realizar una conversión defectuosa, o simplemente mandar un mensaje de error.


NaN e Inf

Toda la base numérica de HOPPER se basa en Ansi C. Los valores nan, inf, -inf, obedecen a las condiciones definidas en el manual de usuario, para cada una de las funciones y operaciones descritas en él.

Es posible detectar y generar datos NAN, con las siguientes instrucciones:

 1) detectar nan: nan?
 2) detectar inf: inf?
 3) generar array de nan: nanarray

También se pueden generar valores NAN y -INF con operaciones aritméticas 0/0 y n/0.

CODIGOS DE ESCAPE

HOPPER usa los siguientes códigos de escape en el despliegue con PRINT:

- \n   Salto de línea
- \t   Tabulación de 8 espacios.
- \"   despliega la comilla dentro de un string.
- \XX  códigos de escape de colores del terminal. Esto será descrito más adelante.

Por ejemplo:

 {"El mensaje:\n\t\"Todos son buenas personas\"\nSerá desplegado en una sola línea."},print

desplegará lo siguente:

  El mensaje:
          "Todos son buenas personas"
  Será desplegado en una sola línea.

SEPARACION DE LINEAS

Para separar líneas largas, se usa el símbolo "\". Existen reglas para su uso. Estas son las siguientes:

1) No pueden separarse strings.

  {"Este es un \
        string..."}    --> esto está mal
  {"Este es un" \
        "string..."}   --> mal. Falta ","
           
  {"Este es un", \
        "string..."}   --> esto está ok.

2) Todo lo demás puede separarse... hasta ahora.

USO DE PARENTESIS

HOPPER no es estricto con el uso de paréntesis, salvo con el preproceso #COMPUTE. Las expresiones matemáticas de HOPPER se escriben de manera natural, por lo que no requieren el uso de paréntesis.

Las macros que contienen argumentos, necesitan llevar paréntesis.

Existen instrucciones que requieren de un argumento, además de obtener datos desde el stack. Dicho argumento puede ser una constante, o bien, puede ser una variable donde se deja un resultado, o de donde se obtiene un dato.


MANEJO DE DATOS

TIPOS DE DATOS

HOPPER maneja tres tipos de datos:

*) números (internamente: int, long y double)
*) cadenas
*) booleanos (0 y 1)

NUMEROS

Los números pueden ser, internamente, de tipo int, long y double, según las operaciones efectuadas, dado que esos son los tipos empleados en la versión 3.0 de Harbour. HOPPER usa la estructura de datos interna de Harbour para manejar los tipos numéricos. En el futuro, si HOPPER abandona su calidad de prototipo, usará su propia estructura de datos, y allí se usarán tipos más extremos, como long long double, y excesos como ese.

Números muy grandes o muy pequeños, serán desplegados en notación científica. Si se usa PRECISION, serán desplegados en toda su extensión, verificando la limitación antes descrita.

En el despliegue de números, HOPPER usa SPRINTF, de Ansi C. Esto tiene sus limitaciones, y a veces, cuando el número es muy grande, se pierde precisión. Por tanto, HOPPER no puede ser usado para realizar cálculos astronómicos, o cálculos nucleares, aunque lo puede intentar.

Si se emplea HOPPER para realizar cálculos de tipo financiero, úsese la PRECISION para obtener números con decimales precisos, aunque aún así podrían existir redondeos visualizados inadecuados. El redondeo interno siempre redondeará el último dígito, y podría entorpecer la visualización del resultado, si la cantidad de decimales es muy grande. Haga la prueba, y verifique si esto sigue así, o si ya se arregló.

Se pueden escribir números en notación científica dentro del script. Asimismo, en la carga de archivos a matrices, aquellos números detectados como notación científica, serán expandidos a su versión decimal.

  1e1, 1E-2, 1.2e5, etcétera.

Se pueden escribir números en base 2, 8 y 16, de la forma siguiente:

  10 = 0xAh = 0x1010b = 0x12o

CADENAS

Las cadenas son tratadas internamente como arreglos de caracteres, aunque todo eso queda oculto a los ojos del programador. HOPPER puede hacer y deshacer con cadenas de caracteres, gracias a funciones prestadas por Harbour, y a funciones de factura propia. En tal sentido, no me equivoco al afirmar que el mejor tratamiento de cadenas, lo tiene Harbour. Echadle un vistazo a ese proyecto.

BOOLEANOS

Los valores booleanos no se pueden calcular como números, salvo que se los convierta a números. Cualquier variable puede ser evaluada lógicamente por las instrucciones lógicas, y convertida a tipo booleano, de acuerdo al siguiente criterio:

*) cadenas:
  Si tiene caracteres = 1 (true)
  Si está vacía       = 0 (false)
 
*) números:
  Si es distinto de 0 = 1 (true)
  Si es 0             = 0 (false)

Se pueden declarar variables de tipo simple como TRUE o FALSE, con las instrucciones:

  true(v)    ==> asigna 1-lógico a "v"
  false(v)   ==> asigna 0-lógico a "v"

Asimismo, se puede añadir un valor lógico, mediante el stack, por medio de las instrucciones:

  true, mov(v)   ==> deja 1-lógico en stack, y luego, se mueve a "v"
  false, mov(v)  ==> deja 0-lógico en stack, y luego, se mueve a "v"

OBSERVACION. Las instrucciones TRUE y FALSE no trabaja con matrices, por el momento.

VARIABLES

Las variables en HOPPER son, por defecto, GLOBALES, es decir, su alcance es todo el programa. No obstante, todas aquellas variables inicializadas luego de la cláusula ".LOCALS", serán tratadas como locales. Si una variable global, es usada dentro de un contexto sin ser inicializada, mantiene su valor y es susceptible de ser modificado. Ejemplo:

  main:
     a=10, b=10
     jsub(label)
     {"Valor global A = ",a,", B = ",b,"\n"}print
     {0}return
 
  .locals
  label:
     a=1, ++b
     {"Valor local A = ",a,", B = ",b,"\n"},print
     back

Imprimirá:

     Valor local A = 1, B = 11
     Valor global A = 10, B = 11

Las variables pueden contener datos de cualquier tipo, durante su ciclo de vida. Ejemplo:

  a=0
  ...
  a="mensaje"
  ...
  a={}, {1,2,3,4,5,6} add row(a)

LIMPIAR VARIABLES

Cuando ocurre que una variable adquiere diferentes valores durante su ciclo de vida, los datos anteriores son descartados, pero no son eliminados. Para "limpiar" esos datos, en particular, cuando antes una variable almacenaba una matriz y luego almacena un dato simple, se puede usar la instrucción CLEAR, cuyo argumento es una variable.

  clear(v)

esta instrucción inicializa "v" en 0, y activa el garbage collector interno.

OBSERVACION. la variable a ser limpiada necesita haber sido inicializada anteriormente.

OBSERVACION 2. El garbage se activa cuando la variable tuvo un valor muy pesado, y se hace necesario limpiarla para liberar memoria (ver "EL GARBAGE COLLECTOR" para más detalles).


INICIALIZACION DE VARIABLES

En HOPPER, el control "=" sirve para inicializar una variable.

No se permite inicializar variables con expresiones compuestas. Por ejemplo:

  x = {2,1}add   --> MALO
  x = x + 1      --> MALO

El control "=" realiza una declaración e inicialización simultanea. Para que una variable pueda ser usada, necesita ser declarada con anticipación.

Ejemplos:

  x = 0     
   inicializa "x" con "0" 
   
  s = "María tenía un corderito"
   inicializa "s" con el string "María tenía..."
  
  x = 0xAh
   inicializa "x" con "10".
  
  b = {}
   inicializa "b" como un array vacío. No puede inicializar una variable como un array con elementos.
   
  M = N
   Si "M" y "N" son matrices, copia la matriz "N" en "M".
  
  M == N
   Copia un puntero a "N" en "M", si ambos son matrices.
   
  M = -1.234
   Si "M" es matriz, la rellena con "-1.234".
  M = 1.234e-2
   Si "M" es matriz, la rellena con "0.01234".
   
  M = "María tenía un corderito"
   Si "M" es matriz, asigna "María tenía un corederito" a cada posición de "M".


OPERADORES SUBORDINADOS

OPERADORES COMUNES

El control "=" tiene variaciones que le permiten alterar el contenido de la variable objetivo, mediante una operación binaria simple. Estos subcontroles, o controles subordinados, son los siguientes:

 HOPPER      EQUIVALENTE
             SINTACTICO (#COMPUTE)
 a += b       a = a + b
 a -= b       a = a - b
 a *= b       a = a * b
 a /= b       a = a / b
 a ^= b       a = a ^ b    potencia
 a \= b       a = a \ b    división entera.
 a %= b       a = a % b    resto módulo
            ESTO NO SE USA
             EN #COMPUTE
 a <<= b      a = a << b   desplaza "b" bits a la izquierda
 a >>= b      a = a >> b   desplaza "b" bits a la derecha
 a |= b       a = a | b    operación OR binario
 a &= b       a = a & b    operación AND binario
 a != b       a = a ! b    operación XOR binario
 

OBSERVACION. La variable "a" puede ser una matriz, ya sea de cadena o numérica (enteros y/o decimales). "b" debe ser un tipo simple, y DEBE SER ENTERO. Si "b" es un número decimal, será convertido (casteado) internamente a entero. Explicación: los controles "X=" son subordinados al control "=", pero son extensiones de "++" y "--" explicados a continuación, y esos controles operan solo con enteros. La exigencia se hace evidente por la aplicación con cadenas. Si quiere operar con números decimales, use las instrucciones, y el stack. Ejemplo: Sea "M" matriz, y contiene "10,0,5,-3,1":

 M += 10      ==> "20,10,15,7,11"
 M *= 2       ==> "20,0,10,-6,2"

Los controles subordinados "+=" y "-=" sirven para realizar operaciones básicas con cadenas. Es decir, son subcontroles sobrecargados. Su uso puede extenderse a matrices. Ejemplo:

 s="ABCDE"
 s+=3        ==> queda: DE
 s="ABCDE"
 s-=3        ==> queda: AB

INCREMENTO Y DECREMENTO

Los controles para incrementar y decrementar una variable, son "++" y "--". Estos controles son un préstamo de Ansi C, y tienen funciones semejantes, aunque hay que tener cuidado. Además, son controles sobrecargados, porque también pueden ser aplicados a cadenas.

Estos controles pueden ser aplicados tanto a tipos simples, como a tipos matriciales. Ejemplo:

  ++x        ==> incrementa en 1 el contenido de "x"
  --x        ==> decrementa en 1
  x++        ==> apila "x" en el stack, y luego, incrementa.
  x--        ==> apila "x" en el stack, y luego, decrementa.

Sea "M" matriz, y contiene "10,0,5,-3,1":

  ++M        ==> "11,1,6,-2,2"
  --M        ==> "9,-1,4,-4,0"

Si "s" es una cadena:

  s="ABCDE"
  ++s        ==> BCDE
  s="ABCDE"
  --s        ==> ABCD

Ejemplos:

  n=10
  n++, print  ==> imprime 10
  {n}  print  ==> ahora, imprime 11

OPERADORES MATEMATICOS

HOPPER no soporta operadores matemáticos, dejando que instrucciones se encarguen de efectuar las operaciones. Recordar que HOPPER es una VM que ejecuta un pseudo-ensamblador. Los operadores aceptados son a nivel del macro-procesamiento de #COMPUTE, es decir, pseudo-operadores, y son los siguientes:

  OPERADORES #COMPUTE                EQUIVALENTES
                              HOPPER              NATURAL
+ suma                 | {a,b} add = b+a | {a} plus(b)  = a+b
- resta                | {a,b} sub = b-a | {a} minus(b) = a-b
* multiplicación       | {a,b} mul = b*a | {a} mulby(b) = a*b
/ división             | {a,b} div = b/a | {a} divby(b) = a/b
\ división entera      | {a,b} idiv= b\a | {a} idivby(b)= a\b
% resto módulo         | {a,b} mod = b%a | {a} mod(b)   = a%b
^ potencia (elevado a) | {a,b} pow = b^a | {a} powby(b) = a^b

En el contexto #COMPUTE, las operaciones son POSTFIJAS.

Estos operadores pueden ser aplicados sobre matrices. Las operaciones sobre estas serán Hadagard.


OPERADORES LOGICOS

HOPPER tampoco soporta operadores lógicos, tan solo en el contexto de #COMPUTE. Los pseudo-operadores son los siguientes:

CONTEXTO                                  EQUIVALENTES  
#COMPUTE                            HOPPER           NATURAL
  ==      Igual que            {a,b}eq? = b == a    {a}eqto(b)   = a == b
  <=      Menor o igual que    {a,b}le? = b <= a    {a}lethan(b) = a <= b
  >=      Mayor o igual que    {a,b}ge? = b >= a    {a}gethan(b) = a >= b
  <>      Distinto que         {a,b}neq?= b <> a    {a}neqto(b)  = a <> b
  <       Menor que            {a,b}lt? = b < a     {a}lthan(b)  = a < b
  >       Mayor que            {a,b}gt? = b > a     {a}gthan(b)  = a > b

Los pseudo-operadores lógicos empleados en HOPPER, contexto #COMPUTE, son los siguientes:

                                            INSTRUCCIONES
                                               HOPPER
 && (and)   (a<b) && (c==0)         and  = {b,a}lt?,{c,0}eq?, and
 || (or)    (a<=b) || (c==0)        or   = {b,a}le?,{c,0}eq?, or
 XOR        xor((a>=b), (c==0))     xor  = {b,a}ge?,{c,0}eq?, xor
 NAND       nand(a>=b), (c==0))     nand = {b,a}ge?,{c,0}eq?, nand
 NOR        nor(a>=b), (c==0))      nor  = {b,a}ge?,{c,0}eq?, nor

El pseudo-operador de negación se antepone a una expresión lógica. Ejemplo:

 !(1==0)    = 1                     not  = {1,0}eq?,not
 !(1)       = 0
 !(0)       = 1

OPERACIONES LOGICAS MATRICIALES

El resultado de realizar comparaciones entre matrices, o entre una matriz y un tipo simple, es una matriz de 0's y 1's, de tipo booleano, del tamaño de la o las matrices comparadas, con las posiciones que contienen un match, con 1-lógico, y con las restantes, con 0-lógico. Dicha matriz resultado, puede ser convertida a matriz numérica, para que pueda ser usada en cálculos y operaciones posteriores.

Si se comparan matrices, estas deben tener el mismo tamaño.

PARAMETROS

HOPPER recibe parámetros desde la línea de comandos, todos de tipo cadena. Dentro del programa, pueden ser convertidos al tipo deseado. Los parámetros se caracterizan con un número entre conchetes, usando el sufijo "&":

 [&1] es el primer parámetro, que contiene el nombre del script.
 [&2] en adelante, se reserva al resto de los parámetros.
 

No existe el parámetro [&0].

Los parámetros no pueden ser inicializados, ni ser recipientes, o sea:

 [&2]=0         no es válido.
 {1},mov([&1])  no es válido.

Los parámetros pueden ser usados para inicializar variables, y pueden ser apilados en el stack. Algunas instrucciones pueden recibir parámetros entre sus argumentos entre paréntesis, pero, al final, se recomienda el uso de variables inicializadas con estos parámetros. Ejemplo:

 v=[&1]                                  es válido.
 {"Nombre del script: ",[&1]}, print     es válido.
 {[&3]},xtonum,mulby (10)                es válido.

La instrucción TOTAL ARG apila en el stack el número total de argumentos pasados al script. El siguiente ejemplo, recorre los parámetros ingresados al programa:

 i=1
 loop:                                       // contexto LOOP
    {"Parámetro [",i,"]=",[&i],"\n"},print    // imprime mensajes
    ++i                                      // incrementa i
    total arg                                // apila total de argumentos
    {i},jle(loop)                            // apila 'i', salta a LOOP si
                                             // i <= total arg

ESTRUCTURA DE UN PROGRAMA

La siguiente es la estructura de un típico programa HOPPER:

[ #include <archivo.hh> | [path/]archivo.hh ]
[ #import archivo-libreria
[ #define DEFNAME[(argumentos)]  cuerpo-de-define [\ 
                                 continuación ] ]
[ #defn   DEFNAME[(argumentos)]  cuerpo-de-define-con-TAGS-macro-programación [\ 
                                 continuación ] ]                
[ #proto NOMBRE_PSEUDO_FUNCION[(arguentos)] ]
[ #context | #context-free <lista-de-contextos> ]
[ #synon | #synonymous  < DEFNAME | CONTEXTO >   <lista-de-sinónimos> ]
main:
  [.stack SIZE-STACK ]
  [.ctrl]
  [ #hl | #fx | #compute | #high-level < { <instrucciones-alto-nivel>
                                       } > | < ( línea-alto-nivel ) > ]
  ...instrucciones-HOPPER...
  [ <label>:
      instrucciones-HOPPER ]
  [ #define DEFNAME[(argumentos)]  cuerpo-de-define [\ 
                                   continuación ] ]
  [ #defn   DEFNAME[(argumentos)]  cuerpo-de-define-con-TAGS-macro-programación [\ 
                                   continuación ] ]
  {<valor-retorno>}return
[.locals]
  [ <contexto> :
    instrucciones-HOPPER
    [instrucciones-alto-nivel]
    back ]

OBSERVACIONES. los comandos de macro-procesamiento #DEFINE y #DEFN pueden ser usados en cualquier parte del programa, pero solo serán visibles desde el momento en que son declarados en adelante.

ETIQUETAS, CONTEXTOS Y PSEUDOFUNCIONES

HOPPER no soporta funciones, porque es un lenguaje de "bajo" nivel. Sin embargo, se pueden simular funciones, así como un nuevo tipo de subprograma llamado "contexto". La clave de ambos, es el uso de "etiquetas".

ETIQUETAS

Una etiqueta es una etiqueta de salto que puede ser invocada por las instrucciones de salto, como JMP, JSUB, GOSUB, y las instrucciones de salto condicionado del tipo Jxx?. Un ejemplo de esto último:

  loop:
     {"I=",i,"\n"}print
     ++i
     {i}jnz(loop)
  {"FIN\n"}print

Salta a LOOP: si el valor de "i" no es cero. Cuando "i" sea cero, se imprime "FIN".

CONTEXTOS

Si la etiqueta termina con la instrucción BACK, dicha etiqueta define un CONTEXTO, y debe ser invocada con JSUB y/o GOSUB, porque BACK devuelve el control del programa a la instrucción siguiente de la invocación. Ejemplo:

  jsub(obtener mensaje), print  // imprime el mensaje del stack
  ...
  
  obtener mensaje:
    {"Hola mundo!\n"}   // mete mensaje en el stack.
  back

La invocación puede suceder hacia arriba. Ejemplo:

  obtener mensaje:
    {"Hola mundo!\n"}   // mete mensaje en el stack.
  back
  :
  jsub(obtener mensaje), print

La invocación a una etiqueta puede ser condicionada, con GOSUB. Ejemplo:

  { edad, 120 } eq? gosub(mensaje)  // salta a MENSAJE si en el stack hay un valor TRUE.
  print
  ...
 
  mensaje:
    {"Es un record Guiness!!!\n"}
  back
  

OBSERVACION. GOSUB requiere un valor, de cualquier tipo. Si no hay nada en el stack, GOSUB arrojará un error.

PSEUDOFUNCIONES

Una pseudofunción se construye con CONTEXTOS, es decir, con etiquetas del tipo LABEL: / BACK. Además, es necesario usar el tag #PROTO o #PROTOTYPE, para armar la llamada a dicha pseudofunción. El identificador debe ser usado anteponiendo un guión bajo. Ejemplo:

  #proto llevargradosaradianes(_X_)
  #define M_PI           3.14159265358979323846
  main:
     a = 30, rad ángulo=0
     _llevar grados a radianes(a), mov(rad ángulo)
     {a," grados = ",rad ángulo," radianes\n"} print
  {0}return
  .locals
  llevar grados a radianes(ángulo)
     {ángulo} mulby 'M_PI', div by '180'
     back

resulta:

  30 grados = 0.523599 radianes

RECURSIVIDAD

Usando pseudofunciones, es posible programar recursividad. Un ejemplo de lo anterior:

  #include <hopper.h>
  #proto   factorial(_v_)
  
  main:
     _factorial(170), mov(f)
     {"\nFactorial de 170 = ",f},print
  {0}return
  
  .locals
  factorial(n)
     if ( {n} lethan '1' )
        {1}
     else
        {n}, _factorial( {n}minus'1' ), mul
     end if
     back
  

resulta:

  Factorial de 170 = 7.25742e+306

OBSERVACION. la estructura IF/ELSE/ENDIF no es implícita de HOPPER, sino, una macro definida en STDIO.HH. Una versión del programa anterior, con otra macro (y más rápida) denominada IIF, es la siguiente:

  #include <hopper.h>
  #proto   factorial(_v_)
  
  main:
     _factorial(170), mov(f)
     {"\nFactorial de 170 = ",f},print
  {0}return
  
  .locals
  factorial(n)
     iif( {n}lethan'1', {1}, {n}; _factorial( {n}minus'1' ); mul) 
     back

OBSERVACIONES.

1. Dentro de una macro, un argumento puede ser simple o compuesto. Cuando es compuesto, las instrucciones deben separarse con ";" (semicolon).

2. Internamente, HOPPER no detecta una llamada recursiva. Cualquier contexto definido después de .LOCALS, intercala instrucciones de llamada a una pila especial de datos usada para estos casos, invisibles al programador, llamadas IPUSH(DATA) y IPOP(DATA), cada vez que se invoca una instrucción JSUB o GOSUB. Estas instrucciones pueden ser usadas en otros contextos, por el programador.

CONTEXTOS DE ALTO NIVEL

Un contexto de este tipo permite definir una llamada especial a un contexto, con sinónimos para dicha llamada. Existen dos tipos de contextos de alto nivel: #CONTEXT y #CONTEXT-FREE. #CONTEXT define una llamada que se invoca consultando el stack por un valor de verdad TRUE: si es FALSE, no hay invocación. Por otro lado, #CONTEXT-FREE define una llamada simple.

  1. CONTEXT, internamente, usa las instrucciones KEEP,GOSUB.
  2. CONTEXT-FREE, internamente, usa la instrucción JSUB.

Ejemplo:

  #include <hopper.h>
  #context-free obtener el cuadrado de pi
  #synon   obtenerelcuadradodepi    elevarpialcuadrado,\
                                    pielevadoalcuadrado,elevapialcuadrado
  
  #define  luegode  emptystack?,not,do{ {"No puedo continuar por datos remanentes "},\
                    throw(1001) }
  #synon   luegode  porotrolado,aparte,ahorabien,finalmente
  
  #define  luego    emptystack?do{ {"No puedo continuar por falta de datos "},\
                    throw(1000) }
  #synon   luego    entonces,yde,yademásde,conloanterior,contodoloanterior,\
                    acontinuación,hechoesto,hechoeso
  
  #define  eimprime     print
  #synon   eimprime     eimprímelo,imprímelo,imprime
  
  #defn    más(_X_)     #IF plus(#ATOMF) #ELSE #CMPLX,add #EIF,
  #synon   más          súmale,suma
  #defn    divididopor(_X_)  #IF divby(#ATOMF) #ELSE #CMPLX,postfix,div,postfix, #EIF,
  #synon   divididopor       divídelopor 
  
  main:
      luego de elevar pi al cuadrado, súmale '2', divídelo por '7'; hecho esto, imprímelo
  {0}return
  
  .locals
  obtener el cuadrado  de   pi    :
     {M_PI} pow by '2'
     back

OBSERVACIONES.

1. #CONTEXT-FREE y #CONTEXT permiten convertir cualquier llamada definida en #SYNON, a su etiqueta principal. En el ejemplo, la llamada "elevar pi al cuadrado" se convierte en "jsub(obtenerelcuadradodepi).

2. Los tags #IF/#ELSE/#EIF permiten decidir en el macro-procesamiento qué tipo de instrucción se aplicará en la transformación, según el tipo de argumento.

3. El tag #ATOMF es saturado por un argumento constante o registro, para ser usado como argumento dentro de una instrucción especial; #CMPLX, es saturado por un argumento compuesto.

4. Los tags anteriores deben ser usados dentro de un tag #DEFN.

5. #SYNON define sinónimos para una determinada macro.

6. Se pueden definir contextos de alto nivel en cualquier idioma.

7. Se pueden definir estilos de programación (natural, semi-formal y formal).

8. Se puede adaptar un analizador de voz para codificar contextos de alto nivel.

CODIGO DE ALTO NIVEL

En HOPPER existen dos tipos de instrucciones: las que recuperan sus argumentos desde el stack, y las que recuperan sus argumentos desde el stack y/o directamente del registro de memoria o variable. Ejemplo de esto:

 TIPO 1: {A, B} add   ==>  instrucción que solo usa el stack.
 TIPO 2: {A} plus (B) ==>  instrucción híbrida.

Todas las instrucciones empleadas en el código de alto nivel, son aquellas del TIPO 1.

Las instrucciones de alto nivel son macro-procesadas y convertidas a instrucciones de "bajo" nivel. Los tag de macro-procesamiento de código de alto nivel son: #HL, #HIGH-LEVEL, #COMPUTE, #FX, las cuatro son sinónimas. Estas instrucciones deben ser puestas dentro de MAIN.

Se admiten dos modalidades en el uso de estos tags:

1. modalidad in-line. Ejemplo:

  #hl ( <instrucciones> )
  

2. modalidad en bloque. Ejemplo:

  #hl {
        <instrucciones>
      }

Ambas modalidades requieren que se escriba una instrucción por línea.

Dentro de la segunda modalidad, se pueden usar las siguientes estructuras de control de alto nivel, que solo pueden usarse dentro del ámbito de alto nivel:

 WHILE/WEND
 DO/UNTIL
 IF/ELSE/ENDIF

Además, se definen las siguientes instrucciones:

 break
 break if
 continue
 continue if

Ejemplos:

  #hl ( a <= 10 ), gosub(siguiente)
  #hl ( x = sqrt(a)^2-1 ), {x},mov(r)
  #hl ( v[i] = n - v[i] * (x:=abs(x-1)) + 1 * x ), print(v)
  #hl ( y *= (x<0) ? (-1) : (x==0) ? 1 : 0; + 0.5 )
  #hl {
     w=0
     b=1
     while ( w<=10 && b )
        print("W=",w," SQRT(",w,")=",sqrt(w),"\n")
        break if (w==5)
        w+=1
     wend
     b = (w==5) ? 0 : 1;
  }

Como se puede apreciar en el ejemplo anterior, dentro del código de alto nivel se puede usar el "operador ternario", "?:", en ambas modalidades.


OBSERVACIONES.

1. Las estructuras de control permiten anidamiento.

2. Algunas instrucciones de alto nivel, aunque poseen un comportamiento semejante a sus homólogas de "bajo" nivel, se desarrollan de manera diferente con la expansión de macros. Por ejemplo:

 ALTO NIVEL      EXPANSION                             HOMOLOGO BAJO NIVEL
 a=0             {0},a=0,mov(a)                        a=0      ( CLET A,0 )
 a+=b            {a,b}add,a=0,mov(a)                   a+=b     ( pINC A,B )
 a=1.0+abs(c)    {1.0}{c}abs,add,a=0,mov(a)            no existe
 a=b=c=5         {5},a=0,cpy(a),b=0,cpy(b),c=0,mov(c)  no existe
 p[i]*=v         [i],get(p),{v},mul,[i],put(p)         no existe
 a==b && !c      {a}{b},eq?,{c},not,and                no existe
 (v==0)?x:y      {v}{0}eq?,jnt(XXX),{x},jmp(FFF),
                 XXX:,{y},FFF:                         no existe
 no existe       no existe                             ++a, --a, a++, a--
 etcétera.

3. Los tags de macro-procesamiento deben iniciar la línea; no se permite intercalar código HOPPER y macro-procesamiento, en ese orden. Ejemplo:

  {x,y}, add, mov(r), #hl ( v=0 )  ==> malo.

Pero:

  {x,y},add, mov(r)
  #hl (v=0)                ==> bien!   
  #hl ( x+y ), mov(r)      ==> bien!
  #hl ( r=x+y ), print(r)  ==> bien!

PRECISION NUMERICA

HOPPER permite definir una precisión numérica especial, para realizar programas que manejen datos de tipo comercial y financiero.

La instrucción TIPO 2, PREC(n), define una precisión de "n" decimales, con 0 <= n <= 13. Para definir una correcta precisión, es necesario establecer PREC antes de cualquier acción de cómputo o asignación.

En el siguiente ejemplo, se usa una macro FOR/NEXT definida en STDIO.HH:

  prec(13)      // maxima precision=13
  a=10.7384738236723
  b=10.2368784932043
  
  for(i=13, {i} gethan (0), --i)
     prec(i),{a," + ",b},{" = ",a,b},add,P_NL,print
  next

despliega:

  10.7384738236723 + 10.2368784932043 = 20.9753523168766
  10.738473823672 + 10.236878493204 = 20.975352316876
  10.73847382367 + 10.23687849320 = 20.97535231687
  10.7384738237 + 10.2368784932 = 20.9753523169
  10.738473824 + 10.236878493 = 20.975352317
  10.73847382 + 10.23687849 = 20.97535231
  10.7384738 + 10.2368785 = 20.9753523
  10.738474 + 10.236878 = 20.975352
  10.73847 + 10.23688 = 20.97535
  10.7385 + 10.2369 = 20.9754
  10.738 + 10.237 = 20.975
  10.74 + 10.24 = 20.98
  10.7 + 10.2 = 20.9
  11 + 10 = 21

NOTACION CIENTIFICA

HOPPER despiega un número en notación científica, cuando este es muy grande o muy pequeño. Ejemplo:

  P_NL,{154},fact,print              // factorial de 154
  P_NL,{154},fact,{1},div, print     // 1/factorial de 154
  P_NL,{0.00009002372},print
  P_NL,{0.0000000000000000000000000000000000000000000000000000000000000000000098332372},print
  

despliega:

  3.08977e+271
  3.23649e-272
  9.00237e-05
  9.83324e-69

OBSERVACION. No se debe usar PREC cuando se despliegan números en notación científica. El comportamiento de HOPPER es feísimo cuando ocurre esto, y no tiene arreglo por el momento. Ejemplo:

  prec(7)
  P_NL,{0.00009002372}
  P_NL,{0.0000000000000000000000000000000000000000000000000000000000000000000098332372}
  P_NL,{154},fact,print 
  P_NL,{154},fact,{1},div, print
  P_NL,{200.3409},print

despliega:

  0.0000900
  0.0000000
  30897696138473488989801101804175762109592920239886
  86780686378410971929858830639948513054861959651721
  97343677454170591046125622992264507779147812945945
  84887321529452753451135576999882313559351694199512
  57660626484274594045595337029525941610447282805209
  8857371784585454551040.0000000
  0.0000000
  200.3409000

<<< NO USE "PREC" CON NUMEROS CIENTIFICOS >>>


INSTRUCCIONES DEL STACK DE TRABAJO

 HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
{A,B,C,...}           Apila datos en el stack. En el        no existe
                      caso de un array, apila el puntero
                      de dicho array.
 
{@A}                  Apila una copia del array A en
                      el stack. El contenido del stack 
                      será diferente al array original;
                      sin embargo, ambos datos están
                      ligados por referencias internas.
                      Es muy difícil marcar la copia en
                      el stack por el Garbage Collector.
                      Usese con cuidado.
 
{[&n]}                 Apila una copia del parámetro "n"
                      en el stack.
 
clearstack            Marca los elementos desreferenciados del
                      stack, para que el Garbage Collector los
                      elimine. Eso ocurre solo con datos muy
                      pesados.
                     
                      IMPORTANTE: ATIENDA ESTAS CONSIDERACIONES.
                      1) Si el dato en el stack no ha sido
                         movido o copiado a una variable, puede
                         invocar al garbage collector, y este
                         funcionará.
                      2) si el dato en el stack ha sido movido
                         o copiado a una variable, la referencia
                         no se pierde (aunque se hubiese aplicado
                         clearstack), y solo podrá ser eliminado
                         cuando aplique, luego, CLEAR sobre la
                         variable.
                      3) Si asigna una variable V a otra W, con
                         W=V, ambas quedan referenciadas, por lo
                         que si hace CLEAR(V), no habrá consecuencias,
                         salvo que haga CLEAR(W), cuando serán
                         todas eliminadas.
                      4) Una porción del array debe ser limpiado
                         antes de limpiar el array original. Si
                         limpia el array original antes de la
                         porción, esta porción no puede ser
                         limpiada por el Garbage. Se puede usar
                         esta modalidad para obtener resultados
                         en operaciones dentro de bucles.
                      5) En resumen, PARA QUE EL GARBAGE FUNCIONE,
                         TODA LA CADENA DE ASIGNACIONES DEBE SER
                         MARCADA, CON CLEAR STACK Y CLEAR, PARA
                         CADA UNA DE LAS INSTANCIAS CREADAS, SI
                         SE USAN MATRICES PESADAS.
 
emptystack?            Devuelve TRUE si el stack de trabajo  isemptystack
                       está vacío.
 
sizestack             Devuelve el número de posiciones
                      usadas del stack.
 
kill                  Elimina el último elemento apilado 
                      en el stack (el top).
 
keep | !              Evita que el elemento ubicado en el
                      head del stack sea consumido por una
                      instrucción.
                      !(n) = mantiene los primeros "n"
                            elementos del head del stack 
                            luego de consumidos.
 
dup                   Duplica el último elemento del stack.
 
.stack n              Define un tamaño de "n" posiciones
                      en el stack.
 
{A}cpy(C)             Copia el contenido del top del stack,
                      a la variable C, sin extraer el dato.
 
{A}mov(C)             Mueve el contenido del top del stack,
                      a la variable C, extrayendo dicho dato.

CONDICIONALES Y SALTOS

INSTRUCCIONES CONDICIONALES

Si en la instrucción de alto nivel existe un "*", significa que el orden de los argumentos es afectado por POSTFIX.

TRUE puede ser 1.
FALSE puede ser 0.

INSTRUCCION DO{}

La instrucción DO{} usa el resultado de las instrucciones descritas a continuación, para ejecutar las instrucciones dentro de las llaves de conjunto, si en el stack de trabajo encuentra TRUE. Ejemplo:

  {1,1}eq?
  do{
     {"Son iguales!"}print 
  }

INSTRUCCIONES HIBRIDAS

Las instrucciones híbridas del tipo {A}instr(B) requieren que B sea un tipo simple, no array.

Las instrucciones condicionales, en el caso de que el argumento B sea un array, devuelven un array booleano.

 HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
 {A,B}eq?              Devuelve TRUE si A = B                iseq(A,B), A==B     (*)
 
 {A}eqto(B)
 
 {A,B}neq?             Devuelve TRUE si A != B               isneq(A,B)          (*)
 
 {A,B}noteq?                                                 isnoteq(A,B), A<>B  (*)
 
 {A}neqto(B)           Devuelve TRUE si A != B
 
 {A,B}lt?              Devuelve TRUE si B<A                  islt(A,B), A<B      (*)
 
 {A}lthan(B)           Devuelve TRUE si A<B
 
 {A,B}le?              Devuelve TRUE si B<=A                 isle(A,B), A<=B     (*)
 
 {A}lethan(B)          Devuelve TRUE si A<=B
 
 {A,B}gt?              Devuelve TRUE si B>A                  isgt(A,B), A>B      (*)
 
 {A}gthan(B)           Devuelve TRUE si A>B
 
 {A,B}ge?              Devuelve TRUE si B>=A                 isge(A,B), A>=B     (*)
 
 {A}gethan(B)          Devuelve TRUE si A>=B
 
 {A}neg?               Devuelve TRUE si A<0                  isneg(A)
 
 {A}pos?               Devuelve TRUE si A>0                  ispos(A)
 
 {A}odd?               Devuelve TRUE si A es par             isodd(A)
 
 {A,B}cin?             Devuelve TRUE si A está contenido     iscin(A,B)
  {A,B}occursin        en B.                                 isoccursin(A,B)
  {A}cin(B)
  {A}occursin(B)
 
 {A,B}ecin?            Devuelve TRUE si A está exactamente   isecin(A,B)
  {A,B}exactoccursin?  contenido en B.                       isexactoccursin(A,B)
  {A}ecin(B)
  {A}exactoccursin(B)
 
 {A}zero?              Devuelve TRUE si A=0                  iszero(A)         
 
 {A}void?              Devuelve TRUE si A es una cadena      isvoid(A)
                       vacía.
 
 {A,B,C}between?       Devuelve TRUE si A está entre B y C   isbetween(A,B,C)
 
 {A}env?               Devuelve TRUE si A es una variable    isenv(A)
                       de entorno.
 
 {A}exist?             Devuelve TRUE si A existe como        isexist(A)
                       archivo.
 
 {A}inf?               Devuelve TRUE si A es Inf, o un       isinf(A)
                       array booleano con 1's en las
                       posiciones Inf.
 
 {A}nan?               Devuelve TRUE si A es NaN, o un       isnan(A)
                       array booleano con 1's en las
                       posiciones NaN.
 
 {A,B}all?             Devuelve TRUE si el valor A está      isall(A,B)
                       en toda la matriz B. A puede ser
                       un número (entero o double), o una
                       cadena.
 
 {A,B}any?             Devuelve TRUE si el valor A está,     isany(A,B)
                       al menos, una vez, en la matriz B.
                       A puede ser un número o una cadena.
 
 kbhit?                Devuelve TRUE si se presionó una      iskbhit
                       tecla.
 
 kbesc?                Devuelve TRUE si se presionó la       iskbesc
                       tecla ESC.
 
 kbctrlc?              Devuelve TRUE si se presionó la       iskbctrlc
                       combinación CTRL-C.                    
 
 {A}numeric?           Devuelve TRUE si A es un número       isnumeric(A)
 
 {A}string?            Devuelve TRUE si A es una cadena      isstring(A)
 
 {A}array?             Devuelve TRUE si A es un array        isarray(A)
 
 error?                Devuelve TRUE si hubo un error        iserror
                       en alguna instrucción de archivo.
                       Esta instrucción se empalma con
                       FILEERROR, que obtiene el mensaje
                       de error de la operación de archivo
                       fallida.

INSTRUCCIONES DE SALTOS

Las siguientes instrucciones usan ETIQUETAS de contexto.

SALTOS SIN RETORNO

 jmp(ETIQUETA)         Salto incondicional
 
 {A,B}jeq(ETIQUETA)    Salta a ETIQUETA si B=A
 
 {A,B}jneq(ETIQUETA)   Salta a ETIQUETA si B!=A 
 
 {A,B}jlt(ETIQUETA)    Salta a ETIQUETA si B<A
 
 {A,B}jle(ETIQUETA)    Salta a ETIQUETA si B<=A
 
 {A,B}jgt(ETIQUETA)    Salta a ETIQUETA si B>A
 
 {A,B}jge(ETIQUETA)    Salta a ETIQUETA si B>=A
 
 {A}jt(ETIQUETA)       Salta a ETIQUETA si A no es 0 (TRUE)
 
 {A}jnt(ETIQUETA)      Salta a ETIQUETA si A es 0 (FALSE)
 
 {A}jv(ETIQUETA)       Salta a ETIQUETA si A es cadena vacía
 
 {A}jnv(ETIQUETA)      Salta a ETIQUETA si A es cadena no vacía
 
 {A}jz(ETIQUETA)       Salta a ETIQUETA si A es cero
 
 {A}jnz(ETIQUETA)      Salta a ETIQUETA si A es distinto de cero
 
 {A}jneg(ETIQUETA)     Salta a ETIQUETA si A es negativo
 
 {A}jpos(ETIQUETA)     Salta a ETIQUETA si A es positivo

SALTOS CON RETORNO

 {A}gosub(ETIQUETA)    Salta a ETIQUETA si A es TRUE (1), o tiene
                       una cadena no vacía, o un número distinto
                       de cero, o un array. Si el stack está vacío,
                       GOSUB arrojará error.
                       Retorna con BACK.
  
 jsub(ETIQUETA)        Salto incondicional a ETIQUETA, y retorna
                       con BACK.

INSTRUCCIONES DE CONVERSION

CONVERSION DE TIPOS

 HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
 {A}xtobool           Convierte A a tipo booleano.          xtobool(A)
                      Si A es:
                      número != 0  ==> TRUE
                      número = 0   ==> FALSE
                      cadena       ==> TRUE
                      cadena vacía ==> FALSE
                      valor lógico ==> no seai hueón.
 
 {A}xtonum            Convierte A a tipo numérico           xtonum(A)
                      Si A="123.4", {A}xtonum ==> 123.4
                      Si A es un número, devolverá el
                      mismo.
                      Si A no es una cadena numérica,
                      entonces, XTONUM devuelve 0.
 
 {A}xtostr            Convierte A a tipo cadena             xtostr(A)
                      Si A es:
                      número      ==> cadena numérica
                      booleano    ==> cadena "1" o "0"
                      cadena      ==> devolverá el mismo.

OTRAS CONVERSIONES

 {A}hex                Convierte el número A a una cadena    hex(A)
                       hexadecimal.
 
 {A}bin                Convierte el número A a una cadena    bin(A)
                       binaria.
 
 sizebin(n)            Establece el largo de una cadena
                       binaria, convertida con BIN.
 
 {A}oct                Convierte el número A a un número     oct(A)
                       octal.
 
 {A}sci, {A}notation   Convierte el número A a una cadena    sci(A)
                       en notación científica.
 
 {A}d2r                Convierte el número A a RADIANES      d2r(A)
 
 {A}r2d                Convierte el número A a GRADOS        r2d(A)
 
 {A}upper              Convierte A a mayúsculas              upper(A)
 
 {A}lower              Convierte A a minúsculas              lower(A)
 
 {A}strtoutf8          Convierte la codificación de          strtoutf8(A)
                       caracteres de A, a UTF8.
 
 {A}utf8tostr          Convierte la codificación de          utf8tostr(A)
                       caracteres de A, a ANSI.

INSTRUCCIONES DE TRATAMIENTO DE CADENAS

Si en la instrucción de alto nivel existe un "*", significa que el orden de los argumentos es afectado por POSTFIX.

 HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
 {A}len                Obtiene el tamaño de la cadena A      len(A)
 
 {A}asc                Obtiene el valor ASCII de A           asc(A)
 
 {A}chr                Obtiene el caracter del valor ASCII   chr(A)
 
                       identificado por A.
 {B,C}countat          Cuenta el número de ocurrencias de    countat(B,C)
                       B en C.
                       C puede ser un array.
 
 {A,B,C}countat        Cuenta el número de ocurrencias de    countat(A,B,C)
                       B en C, omitiendo A caracteres.
                       C puede ser un array.
  
 {B,C}findat           Obtiene la posición de la última      findat(B,C)
                       ocurrencia de B en C.
 
 {A,B,C}findat         Obtiene la posición de la A           findat(A,B,C)
                       ocurrencia de B en C.
                       C puede ser un array.
 
 {A,B}find             Obtiene la posición de la primera     find(A,B)
                       ocurrencia de A en B.
                       C puede ser un array.
 
 multipasson           Activa el multipaso en la búsqueda    multipasson
                       con FINDAT y COUNTAT.
 
 multipassoff          Desactiva multipaso.                  multipassoff
 
 {A}trim               Quita espacios laterales de A         trim(A)
 
 {A}trimright          Quita espacios a la derecha de A      trimright(A)
 
 {A}trimleft           Quita espacios a la izquierda de A    trimleft(A)
 
 {A,B,C}padcenter      Centra la cadena C entre un espacio   padcenter(A,B,C)
                       de B caracteres, rellenando con el
                       caracter A.
                       A y B pueden ser arrays, pero el largo
                       de B debe ser el número de columnas
                       de C.
 
 {A,B,C}padright       Ajusta la cadena C entre un espacio   padright(A,B,C)
                       de B caracteres, rellenando con el
                       caracter A, hacia la derecha.
                       A y B pueden ser arrays, pero el largo
                       de B debe ser el número de columnas
                       de C.
 
 {A,B,C}padleft        Ajusta la cadena C entre un espacio   padleft(A,B,C)
                       de B caracteres, rellenando con el
                       caracter A, hacia la izquierda.
                       A y B pueden ser arrays, pero el largo
                       de B debe ser el número de columnas
                       de C.
 
 {A,B}replicate
 {A}replyby(B)         Replica la cadena A, B veces.         replicate(A,B)
                       A puede ser un array.
 
 {A,B}cat              Concatena A y B.                      cat(A,B)
 
 {A,B}cut              Corta la cadena B desde la primera    cut(A,B)
                       ocurrencia del caracter A, desde la
                       izquierda hacia la derecha.
                       A y B pueden ser arrays.
 
 {A,B}rcut             Corta la cadena B desde la última     rcut(A,B)
                       ocurrencia del caracter A, desde la
                       derecha hacia la izquierda.
                       A y B pueden ser arrays.
 
 {A,B,C},copy          Obtiene una subcadena de C, de        copy(A,B,C)
                       tamaño A, contando desde B.
                       C y B pueden ser arrays.
 
 {C,D,E}transform      Cambia todas las instancias de C      transform(C,D,E)
                       en E, con D.
                       C, D y E pueden ser arrays.
 
 {B,C,D,E}transform    Cambia todas las instancias de C      transform(B,C,D,E)
                       en E, con D, contando desde la 
                       ocurrencia B.
                       C, D y E pueden ser arrays.
 
 {A,B,C,D,E}transform  Cambia todas las instancias de C      transform(A,B,C,D,E)
                       en E, con D, contando desde la
                       ocurrencia B, hasta la ocurrencia A.
                       C, D y E pueden ser arrays.
 
 {A,B,C,D}replace      Reemplaza A caracteres desde la       replace(A,B,C,D)
                       posición B, de D, por C.
                       D puede ser un array.
 
 {A,B,C}insert         Inserta la cadena B en C, desde la    insert(A,B,C)
                       posición A.
                       C y B pueden ser arrays.
 
 {A,B,C}delete         Borra A caracteres de C, desde la     delete(A,B,C)
                       posición B.
                       C y B pueden ser arrays.
 
 {A,B}deletechar       Borra en B los caracteres indicados   deletechar(A,B)
                       en A. B debe ser un tipo simple.
 
 {A,B}onechar          Deja solo una ocurrencia de cada      onechar(A,B)
                       caracter de A, en B.
                       B debe ser un tipo simple.
 
 {A,B}onlychar         Deja en B solo los caracteres         onlychar(A,B) 
                       señalados en A.
 
 {A,B,C}poschar        Devuelve la posición en C donde       poschar(A,B,C)
                       cambia la secuencia de un mismo 
                       carcater B. A indica dirección:
                       1 = de izquierda a derecha.
                       0 = de derecha a izquierda.
                       Ejemplo:
                       {1," ","   Hola mundo!  "}poschar
                       ==> 4
                       {0," ","   Hola mundo!  "}poschar
                       ==> 14
 
 {A,B,C}rleft          Reemplaza cada caracter A con el      rleft(A,B,C)
                       caracter B de C, a la izquierda
                       Ejemplo:
                       {" ",".","   mensaje   "}rleft
                       ==> ...mensaje
 
 {A,B,C}rright         Reemplaza cada caracter A con el      rright(A,B,C)
                       caracter B de C, a la derecha
                       Ejemplo:
                       {" ",".","   mensaje   "}rleft
                       ==>    mensaje...
                     
 {A,B,C}rall           Reemplaza cada caracter A con el      rall(A,B,C)
                       caracter B de C, a la derecha
                       Ejemplo:
                       {" ",".","   mensaje   "}rleft
                       ==> ...mensaje...
 
 {A}reverse            Reversa una cadena. No acepta         reverse(A)
                       arrays, solo tipo cadena simple.
 
 countlines(A)         Cuenta el total de líneas de la       no existe
                       cadena A.
 
 join(C)               Concatena todos los elementos del     no existe
                       stack, en la cadena C. Tipos distintos
                       a cadena, serán convertidos en el 
                       proceso.
                       JOIN es sensible al separador de tokens
                       definido por TOKSEP.
                       JOIN no concatena elementos arrays.
 
 {A,B,C}mask           Enmascara la cadena C con el patrón   mask(A,B,C)
                       B, rellenando los espacios remanentes
                       con el caracter A.
                       C puede ser un array.
                       Un caracter no nulo de C es representado 
                       por el símbolo "#" dentro del patrón.
                       Ejemplo:
                       {"-","####.###-###","1ACW025"},mask
                       ==> ---1.ACW-025 
 
 {A,B}money            Convierte el número B a formato       money(A,B)
                       moneda, con A decimales.
                       B puede ser un array.
                       Ejemplo:
                       {2,987678.356},money
                       ==> 987,678.36
 
 {A,B}saturate(C)      Reemplaza cada campo en C, con        no existe
                       cada token en A, separados por B.
                       Cada campo debe ser indizado,
                       iniciando en "0":
                       $0, $1, $n. 
                       Ejemplo 1:
                       
                       A = "Juanito Pérez,5.666.789-K,Calle 7 S/N,Lo Prado"
                       C  = "Yo, $0, RUT nº $1,\nDirección particular $2, comuna $3"
                       {A,","}saturate(C), str to utf8,{"\n"}, print
                       
                       imprime:
                     
                       Yo, Juanito Pérez, RUT nº 5.666.789-K,
                       Dirección particular Calle 7 S/N, comuna Lo Prado
                     
                       La variable final "C" puede ser "largo-posición":
                       
                       linea = "$0:25L$1:15L$2:20L$3:30R"
                       
                       Campo: $n:M[LRC]
                     
                       donde:
                       $n  = número de campo.
                       M   = tamaño del campo a rellenar.
                       LCR = ajusta el campo hacia la izquierda (L), el centro (C), y
                             la derecha (R). 
 
 {A,B}saturate(C)      Reemplaza cada campo en C, con los
                       tokens leídos desde A escritos en
                       largo-posición, cuyos tamaños son
                       señalados por el array B.
                      
                       La variable final "C" puede ser una cadena simple:
                       
                          C = "$0, $1, $2 \n $3"
                     
                       como también, largo-posición:
                        
                          C = "$0:25L$1:15L$2:20L$3:30R"

OBSERVACION. La instrucción SATURATE está pensada para ser usada en combinación con lectura y escritura de archivos de texto, es decir, para transformación de datos.

OBSERVACION 2. Existen otras instrucciones dentro de la categoría de procesamiento de cadenas, como las EXPRESIONES REGULARES o TOKENS, pero serán vistas en las secciones dedicadas a estas.

INSTRUCCIONES MATEMATICAS

OPERACIONES ARITMETICAS

  HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 {A,B}add              Suma B+A
 
 {A}plus(B)            Suma A+B, B debe ser tipo simple
 
 {A,B}sub              Resta B-A
 
 {A}minus(B)           Resta A-B, B debe ser tipo simple
 
 {A,B}mul              Multiplica B*A
 
 {A}mulby(B)           Multiplica A*B, B debe ser tipo simple
 
 {A,B}div              Divide B/A
 
 {A}divby(B)           Divide A/B, B debe ser tipo simple
 
 {A,B}idiv             División entera [B/A]
 
 {A}idivby(B)          [A/B]
 
 {A,B}pow, power       Eleva a potencia B^A
 
 {A}powby(B)           A^B
 
 {A,B}mod, module      Resto módulo B mod A
 
 {A}mod(B)             A mod B
 
 {A,B}sqrdiff          Calcula la diferencia de los          sqrdiff(A,B)
                       cuadrados de A y B.
 
 {A,B}aqradd           Calcula la suma de los cuadrados      sqradd(A,B)
                       de A y B.
 
 {A,B}hypot            Calcula la hipotenusa, con la         hypot(A,B)
                       fórmula pitagórica C=sqrt(A^2+B^2).
 
 {...}mulall           Multiplica todos los datos que se     mulall(A,B,...)
                       encuentren en el stack. Los datos
                       deben ser numéricos.
 
 {...}sumall           Suma todos los datos que se           sumall(A,B,...)
                       encuentren en el stack. Los datos
                       deben ser numéricos.

FUNCIONES MATEMATICAS

 {A}log                Logaritmo natural                     log(A)
 
 {A}log2               Logaritmo base-2                      log2(A)
 
 {A}log10              Logaritmo base-10                     log10(A)
 
 {A}exp                Función exponencial base-e            exp(A)
 
 {A}exp2               Función exponencial base-2            exp2(A)
 
 {A}exp10              Función exponencial base-10           exp10(A)
 
 {A}sign               Devuelve 1 si A>0, -1 si A<0,         sign(A)
                       y 0 si A=0
 
 {A}abs                Devuelve el valor absoluto de A       abs(A)
 
 {A}sqrt               Devuelve la raíz cuadrada de A        sqrt(A)
 
 {A}cbrt               Devuelve la raíz cúbica de A          cbrt(A)
 
 {A}int                Castea A a entero largo               int(A)
 
 {A}floor              Función piso de A                     floor(A)
 
 {A}ceil               Función techo de A                    ceil(A)
 
 {A,B}round            Redondea B a la posición decimal A    round(A,B)
 
 {A}roundby(B)         Redondea A a la posición decimal B
 
 {A}trunc              Trunca A                              trunc(A)
 
 {A}lennum             Devuelve el número de dígitos de      lennum(A)
                       la parte entera de A.
 
 {A}fact               Devuelve el factorial de A            fact(A)
 
 {A}seed               Establece una semilla para iniciar    seed(A)
                       el motor de números aleatorios de
                       la función rand().
                     
 {A}rand               Devuelve un número aleatorio          rand(A)
                       uniforme entre 0 y 1, multiplicado
                       por A. A=1, devuelve un decimal.
 
 {A,B}max              Devuelve el máximo entre A y B. Si    max(A,B)
                       A es tipo simple, y B es array, 
                       devuelve un array con el mayor entre
                       A y cada elemento del array.
                       Si A y B son arrays, deben tener la
                       misma dimensión.
 
 {A,B}min              Devuelve el mínimo entre A y B.       min(A,B)
                       Misma descripción que MAX.
 
 {A,B}mulmat           Devuelve la multiplicación            mulmat(A,B)
                       matricial entre A y B.

FUNCIONES TRIGONOMETRICAS

 {A}sin                Seno de A, A radianes                 sin(A)
 
 {A}cos                Coseno de A                           cos(A)
 
 {A}tan                Tangente de A                         tan(A)
 
 {A}sinh               Seno hiperbólico de A                 sinh(A)
 
 {A}cosh               Coseno hiperbólico de A               cosh(A)
 
 {A}tanh               Tangente hiperbólica de A             tanh(A)
 
 {A}arcsin             Arcoseno de A, -1 <= A <= 1           arcsin(A)
 
 {A}arccos             Arcocoseno de A, -1 <= A <= 1         arccos(A)
 
 {A}arctan             Arcotangente de A                     arctan(A)
 
 {A}arcsinh            Arcoseno hiperbólico de A             arcsinh(A)
 
 {A}arccosh            Arcocoseno hiperbólico de A           arccosh(A)
 
 {A}arctanh            Arcotangente hiperbólica de A         arctanh(A)

CONSTANTES MATEMATICAS

Las siguientes constantes están definidas en HOPPER.H

M_SQRT2              Raíz cuadrada de 2        1.41421356237309504880
M_SQRT3              Raíz cuadrada de 3        1.73205080756887729352
M_SQRT5              Raíz cuadrada de 5        2.23606797749978969640
M_PHI                Phi                       1.61803398874989484820
M_E                  Número e                  2.7182818284590452354
M_LOG2E              Logaritmo base 2 de e     1.4426950408889634074
M_LOG10E             Logaritmo base 10 de e    0.43429448190325182765
M_LN2                Logaritmo base e de 2     0.69314718055994530942
M_LN10               Logaritmo base e de 10    2.30258509299404568402
M_PI_2               PI medio                  1.57079632679489661923
M_PI_4               PI cuarto                 0.78539816339744830962
M_1_PI               1 sobre PI                0.31830988618379067154
M_2_PI               2 sobre PI                0.63661977236758134308
M_PI                 PI                        3.14159265358979323846
M_2_SQRTPI           2 sobre raíz de PI        1.12837916709551257390
M_SQRT1_2            1 sobre raíz de PI        0.70710678118654752440
M_NAN
M_INF

EXPRESIONES REGULARES

Las expresiones regulares en HOPPER están basadas en la librería REGEX de Linux. La documentación, flags y errores, describen el comportamiento de las funciones siguientes.

Constantes usadas por estas funciones, están definidas en HOPPER.H.

CONSTANTES ESPECIALES

Definidas en HOPPER.H, facilitan la compilación de expresiones más usuales.

 R_NUMBERS    para compilar expresiones numéricas.
 R_EMAILS     para compilar expresiones e-mail.
 R_IPS        para compilar expresiones de direcciones IP.
 HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 {F,E}regcompile(P)    Compila una expresión regular E,
                       y asigna el puntero de dicha
                       expresión a P.
                       Ejemplo:
 
                       flag=REG_EXTENDED
                       flag |=REG_NEWLINE
                       flag |= REG_NOSUB
                       T1=0
                       {flag,R_IPS} reg compile(T1)
 
 {F,P,S}regvalid       Si la expresión regular fue           regvalid(F,P,S)
                       compilada con flag REG_NOSUB,
                       REGVALID validará S según P, 
                       y devolverá TRUE(1) o FALSE(0).
                       Ejemplo:
 
                       flag=0
                       {flag,T1,"255.255.0.0"},reg valid
 
 {F,N,P,S}regmatch     Devuelve un array con:                regmatch(F,N,P,S)
                       posición inicial del match, 
                       posición final del match
                       cadena detectada.
                       donde F es el flag de compilación,
                       N es el número de matches a
                       detectar, P es el puntero a la
                       compilación, y S es la cadena a
                       chequear.
                       Ejemplo:
 
                       flag compile=REG_EXTENDED
                       {flag compile,R_NUMBERS} reg compile(T)
 
                       flag match=0
                       número de matches = 10
                       {flag match,número de matches,\
                       T,"123.001, MSG, 2.34e10,-12"},reg match
 
 regfree(P)            Libera el puntero de compilación P,
                       eliminando la compilación de la 
                       memoria.

FECHAS, HORAS Y CONTROL DE TIEMPO

Las funciones date y datenow usan constantes definidas en HOPPER.H. DATENOW es una instrucción del tipo NAVAJA SUIZA, que posee una serie de herramientas para el tratamiento básico de fechas y horas.

 HOPPER               DESCRIPCION                                       
 -----------------------------------------------------------------------------------
 datenow(CTE)          Procesa fecha y hora actual. CTE puede ser:
                       TODAY         Devueve fecha y hora, de la forma:
                                     12/11/2020, 01:18:03:30
 
                       DTNORMAL      Devuelve fecha y hora, de la forma:
                                     12 de Noviembre de 2020,01:18:03:30
 
                       DATESTR       Devuelve la fecha: 12/11/2020
                       TIMESTR       Devuelve la hora: 01:18:03:30
                       DATESTACK     Devuelve la fecha en forma stack: 20201112
                       TIMESTACK     Devuelve la hora en forma stack: 303181
                       GETYEAR       Devuelve el año
                       GETMONTH      Devuelve el número del mes
                       GETWEEK       Devuelve el índice de la semana del año
                       GETDAY        Devuelve el día del mes
                       GETHOUR       Devuelve la hora
                       GETMINUTES    Devuelve los minutos
                       GETSECONDS    Devuelve los segundos
                       GETDAYSTR     Devuelve el nombre del día
                       GETMONTHSTR   Devuelve el nombre del mes
                       GETDAYSMONTH  Devuelve el número de los días del mes
                       GETDAYWEEK    Devuelve el índice del día de la semana, 
                                     iniciando en domingo = 1.
                       GETDAYYEAR    Devuelve el día del año
                       ISLEAPYEAR    Devuelve TRUE si es año bisiesto
                       ISTIMEVALID   Devuelve TRUE si la hora es válida.
 
 {F}date(CTE)          Procesa fecha contenida en F.
 
 {H}date(CTE)          Procesa hora contenida en F.
 
 {H2,H1}elaptime       Devuelve el tiempo transcurrido entre H1 y H2, en formato
 
 {H2,H1}elapsedtime    HH:MM:SS. Ejemplo:
                       {"18:04:21","08:15:23"}, elaptime
                       ==> 14:11:02
 
 {F2,F1}daysdiff       Devuelve los días transcurridos entre F1 y F2.
                       Ejemplo:
                       {"12/6/2020"},datenow(DATESTR), daysdiff
                       ==> 153
 
 {F,N}dateadd          Suma N días a la fecha F. Si N<0, resta.
                       Ejemplos:
                       datenow(DATESTR),{-15},date add
                       ==> 28/10/2020
                       datenow(DATESTR),{15},date add
                       ==> 27/11/2020
 
 seconds               Devuelve los segundos transcurridos desde la medianoche.
                       El stack debe estar vacío.
 
 {H}seconds            Devuelve la hora H en segundos.
                       Ejemplo:
                       {"18:35:09"}seconds   ==>  66909
 
 {S}sectotime          Transforma los segundos en formato hora.
                       Ejemplo:
                       {66909},sec to time   ==>  "18:35:09"
 
 timecpu(T)            Devuelve el tiempo de procesador usado por el programa
                       HOPPER, y lo deja en T.
 
 {N}microsecs          Realiza una pausa de N microsegundos.
 
 {N}sleep              Realiza una pausa de N segundos.
 
 {N}timer(T)           Devuelve TRUE si T menos T-actual es mayor o igual a N,
                       en milisegundos.
 
 clockpersec           Devuelve los ciclos por segundo.
 
 {T,M,A}cal(C)         Genera un calendario del mes M, año A. la constante C
                       determina los meses a procesar.
                       C = 0,       procesa mes actual. Descarta lo que exista
                                    en el stack.
                       C = 1..99,   procesa mes, más/menos C meses.
                       C = 100-200, procesa mes, más C-100 meses.
 
                       Ejemplos:
                       {0}cal(0)    genera el calendario de la fecha actual, y
                                    lo despliega por pantalla. Similar a cal(0).
 
                       {1}cal(0)    genera calendario actual, pero lo guarda en
                                    un array de 2 dimensiones.
 
                       datenow(GETMONTH),datenow(GETYEAR),cal(2)
                                    genera un calendario con la fecha actual,
                                    más/menos 1 mes, y lo guarda en un array.
 
                       datenow(GETMONTH),datenow(GETYEAR),cal(102)
                                    genera un calendario con la fecha actual, más
                                    1 mes.
 
                       {6,2020}cal(102)  genera un calendario del mes de junio de
                                         2020, más julio.
 
                       {6,2020}cal(2)    genera un calendario del mes de junio de
                                         2020, junto con abril, mayo, julio y
                                         agosto.
 
                       {6,2020}cal(100)  genera un calendario del mes de junio de
                                         2020. Idem a cal(101).

SOCKETS

HOPPER tiene un sistema básico de manejo de sockets, que encapsula las funciones de sockets de C. En HOPPER.H se encuentran las definiciones macro para socket.

La instrucción SOCKET es del tipo NAVAJA SUIZA, con la cual se pueden realizar conexiones básicas, tanto TCP como UDP.

SERVER TCP

 HOPPER               DESCRIPCION
 -----------------------------------------------------------------------------------
 {N,P}socket(OPENTCPSRV)
 {F}socket(CLOSESOCK)
                       Abre un socket en modo server, y devuelve un identificador de
                       socket. También, cierra un socket identificado por F. 
                       Para más información, ver el ejemplo al final de este ítem.
 
                       N = número de conexiones
                       P = puerto
                       CODIGO = ver lista de códigos.
   
 accept(F)             Acepta una conexión entrante, con identificador de socket F,
                       y devuelve un identificador de cliente conectado.
 
 {A}send(F)            Envía una cadena A al socket identificado por F.
 
 {S}recv(F)            Recibe una cadena desde el socket F, de tamaño S. La función
                       devuelve:
                       1) mensaje del cliente
                       2) número de IP del cliente
                       3) tamaño del mensaje recibido.
 
    EJEMPLO:
 
    #include <stdio.hh>
 
    main:
      fd=0,fdc=0
      {","}tok sep
 
      {5,10000}socket (OPENTCPSRV)        /* abre socket tcp modo server: puerto
                                           10000, número de conexiones: 5 */
      mov(fd)                             // obtengo el descriptor
      accept(fd)                          // acepta conexión
      mov(fdc)                            /* obtiene identificador de cliente 
                                           conectado */
      {"Bienvenido a mi servidor Hopper!"},
      send(fdc)                           // envía mensaje al cliente
      {100}recv(fdc)                      // recibe respuesta del cliente
  
      s="",join(s)                        /* junta la respuesta del cliente y del
                                           socket, en la variable "s" */
      {s,"\n"}print                       // imprimer mensaje
      {"Respuesta a su solicitud..."}
      send(fdc)                           // envía mensaje réplica al cliente
      {fdc}socket(CLOSESOCK)              // cierra socket del cliente
      {fd}socket(CLOSESOCK)               // cierra socket del server
    {0}return

CLIENTE TCP

 HOPPER               DESCRIPCION
 -----------------------------------------------------------------------------------
 {IP,P}socket(OPENTCPCLI)
 {F}socket(CLOSESOCK)
                       Abre un socket en modo cliente, y devuelve un identificador
                       de socket. También, cierra un socket.
 
                       IP = IP del server. Ejemplo: "127.0.0.1"
                       P = puerto
                       F = identificador de socket
 
 connect(F)            Se conecta al server. Si el server no está en línea, devuelve 
                       un error.
 
    EJEMPLO:
 
    #include <stdio.hh>
    
    main:
      fd=0
      {"\n"}tok sep
  
      {"127.0.0.1",10000}socket(OPENTCPCLI)    // abro socket modo cliente
      mov(fd)                                  // obtengo descriptor
      connect(fd)                              // me conecto al server
      {100}recv(fd)                            // recibo mensaje enviado por server
 
      s="",join(s)
      {s,"\n"}print                            /* imprime mensaje, ip fuente,
                                                  numero de bytes recibidos */
      {"Solicito algo del SERVER..."}send(fd)  // envio una solicitud al server
      {100}recv(fd)                            /* recibo mensaje respuesta del
                                                  server */
      join(s),{s,"\n"}print
      {fd}socket (CLOSESOCK)
    {0}return

SERVER UDP

 HOPPER               DESCRIPCION
 -----------------------------------------------------------------------------------
 {P}socket(OPENUDPSRV) 
 {F}socket(CLOSESOCK)  Abre un socket server UDP, y devuelve un descriptor para ese
                       socket. También lo cierra.
 
 {S}recvfrom(F)        Recibe un mensaje desde el socket F, de tamaño S.
 
 {A}sendto(F)          Envía un mensaje A, al socket F.
 
    EJEMPLO:
    
    main:
      fd=0
      {"\n"}tok sep
  
      {10000}socket (OPENUDPSRV)       /* abre socket UDP modo server: puerto 
                                          10000 */
      mov(fd)                          // obtengo el descriptor
      {100}recvfrom(fd)                // recibo mensaje desde el cliente
  
      s="",join(s)
      {s,"\n"}print                    /* imprime mensaje */
      {"Respuesta a su solicitud..."}
      sendto(fd)                       // envía respuesta al cliente
 
      {fd}socket(CLOSESOCK)            // cierra socket server
    {0}return

CLIENTE UDP

 HOPPER               DESCRIPCION
 -----------------------------------------------------------------------------------
 {IP,P}socket(OPENUDPCLI)
 {F}socket(CLOSESOCK)
                       Abre un socket UDP en modo cliente, y devuelve su descriptor.
                       También cierra un socket.
 
    EJEMPLO:
    
    main:
      fd=0
      {"\n"}tok sep
  
      {"127.0.0.1",10000}socket(OPENUDPCLI)  // abro socket modo cliente
      mov(fd)                                // obtengo descriptor
 
      {"Solicito algo del SERVER..."}
      sendto(fd)                             // envio una solicitud al server
      {100}recvfrom(fd)                      // recibo respuesta del server
      s="",join(s),{s,"\n"}print
      {fd}socket (CLOSESOCK)                 // cierro socket cliente.
    {0}return

LISTA DE CODIGOS

OPENTCPSRV           Abre un socket en modo TCP server.
OPENTCPCLI           Abre un socket en modo TCP cliente.
OPENUDPSRV           Abre un socket en modo UDP server.
OPENUDPCLI           Abre un socket en modo UDP cliente.
CLOSESOCK            Cierra un socket.

TOKENS

 HOPPER               DESCRIPCION
 -----------------------------------------------------------------------------------
 {A}toksep             Establece un separador de token, el cual será usado por $, $$,
                       totaltoken y print using token.
                       Ejemplo:
                       {","}toksep
                     
                       Por defecto, el separador de token es espacio en blanco.
 
 gettoksep             Obtiene el separador de token activo, y lo deja en el stack.
 
 {A}gettoken(B) 
 {A}$(B)               Obtiene el token A de la cadena B.
                       Ejemplo:
                       s="María tenía un corderito blanco"
                       {4}$(s)           ==>  corderito
                       {4}get token(s)   ==>  corderito
  
 {A,B}modtoken(C)
 {A,B}$$(C)            Modifica el token B de la cadena C, con la cadena A.
                       Ejemplo:
                       s="María tenía un corderito blanco"
                       {"lorito",4}$$(s)          ==>  María tenía un lorito blanco
                       {"lorito",4}mod token(s)   ==>  María tenía un lorito blanco
 
 totaltoken(C)         Devuelve la totalidad de tokens existentes en la cadena C.
                       Ejemplo:
                       s="María tenía un corderito blanco"
                       {" "}toksep
                       total token(s)   ==>  5

TERMINAL Y SISTEMA

FUNCIONES DE TERMINAL

 HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 echo                  Imprime todo el stack en pantalla     echo()
                       Ejemplo:
                       {"Valor: ",200,"otro",100.9}echo
                       ==> Valor: 200otro100.9
 
 print                 Imprime todo el stack en pantalla     print()
                       {"Valor: ",200,"otro",100.9}print
                       ==> Valor: 200otro100.9
                     
 printusingtoken       Imprime todo el stack, usando el      printusingtoken()
                       separador de token definido en 
                       toksep. Ejemplo:
                       {";"}toksep
                       {"Valor: ",200,"otro",100.9}print using token
                       ==> Valor: 200;otro;100.9
                     
 show                  Imprime el stack, sin consumirlo.     show()
 
 escape                imprime una secuencia de escape.
                       El primer caracter debe ser "[".
                       Internamente, se añade el caracter
                       de escape "\033".     
                       Ejemplo:
                       {"[?25l"}, escape
                       ==> esconde el cursor.
 
 puts(A)               Imprime A en pantalla.
 
 {}puts                imprime el TOP del stack.
 
 {X,Y}goxy             Posiciona el cursor en la posición
                       fila X, columna Y, en pantalla.
 
 {X}gox                Posiciona el cursor en la fila X.
 
 {Y}goy                Posiciona el cursor en la columna Y.
 
 strtoutf8             Convierte el string a codificación UTF8.
 
 utf8tostr             Convierte el string a codificación ANSI.
 
                       NOTA: STRTOUTF8 y UTF8TOSTR no están definidas
                       para trabajar con matrices. Para eso, puede
                       hacer uso de la macro ITERATOR, y trabajar
                       sobre cada elemento de una matriz.

CODIGOS DE ESCAPE

Los siguientes códigos de escape se introducen dentro de una cadena de caracteres.

 \n                    Inserta un salto de línea en la cadena.
                       Ejemplo:
                       {"\nValor: ",v,"\n"}print
                     
 \t                    Inserta una tabulación en la cadena.
 
 \"                    Despliega el caracter comilla en la 
                       cadena. Ejemplo:
                       {"Mensaje \"especial\""}print
                       ==> Mensaje "especial"
 
 \e
 \033                  Inserta un código de escape (27).

CONSTANTES TERMINAL

Las constantes P_xx se introducen en una cadena de caracteres; las constantes P_XX, son independientes. La constante E_EXECNULL puede ser concatenada a una llamada a sistema. Estas constantes son macros definidas dentro de stdio.hh.

P_NL                 {"\n"}
P_nl                 "\n"
P_TAB                {"\t"}
P_tab                "\t"
E_EXECNULL           " </dev/null >/dev/null 2>&1 &"

CODIFICACION DE COLOR

Las instrucciones PRINT, PRINT USING TOKEN, ECHO y SHOW, pueden imprimir texto en pantalla mediante una codificación de color basada en códigos de escape internos, los cuales son los siguientes:

CODIGO      CODIGO
HOPPER     TERMINAL    DESCRIPCION
------------------------------------------------------------------
\BGLGR     \e[47m      Activa color background ligth grey
\BGDGR     \e[100m     Activa color background dark grey
\BGLR      \e[101m     Activa color background ligth red
\BGLG      \e[102m     Activa color background ligth green
\BGLY      \e[103m     Activa color background ligth yellow
\BGLB      \e[104m     Activa color background ligth blue
\BGLM      \e[105m     Activa color background ligth magenta
\BGLC      \e[106m     Activa color background ligth cyan
\BGW       \e[107m     Activa color background white
\BGBK      \e[40m      Activa color background black
\BGR       \e[41m      Activa color background red
\BGG       \e[42m      Activa color background green
\BGY       \e[43m      Activa color background yellow
\BGB       \e[44m      Activa color background blue
\BGM       \e[45m      Activa color background magenta
\BGC       \e[46m      Activa color background cyan
 
\CUR       \e[3m       Activa atributo CURSIVA
\ENF       \e[1m       Activa atributo ENFATIZADO
\UL        \e[4m       Activa atributo UNDERLINE
\BLK       \e[5m       Activa atributo BLINK
\INV       \e[7m       Activa atributo INVERT
 
\DGR       \e[90m      Activa color dark grey
\LGR       \e[37m      Activa color ligth grey
\LR        \e[91m      Activa color ligth red
\LG        \e[92m      Activa color ligth green
\LY        \e[93m      Activa color ligth yellow
\LB        \e[94m      Activa color ligth blue
\LM        \e[95m      Activa color ligth magenta
\LC        \e[96m      Activa color ligth cyan
\W         \e[97m      Activa color white
\BK        \e[30m      Activa color black
\R         \e[31m      Activa color red
\G         \e[32m      Activa color green
\Y         \e[33m      Activa color yellow
\B         \e[34m      Activa color blue
\M         \e[35m      Activa color magenta
\C         \e[36m      Activa color cyan
 
\OFF       \e[0m       Desactiva todos los atributos y colores.

Ejemplo:

   {"Codigo \Y\ENF","ABC-123","\OFF aceptado"}print
   

despliega el texto "Codigo ABC-123 aceptado", con "ABC-123" con atributo enfatizado y de color amarillo.

FUNCIONES DE LLAMADA A SISTEMA

 {}execv               Ejecuta una cadena de comando, sin
                       esperar un resultado.
                       Ejemplo:
                       {"ls -lstar"},execv,print
                       ==> Muestra el directorio actual.
 
 {}exec                Ejecuta una cadena de comando, y
                       guarda el resultado en el stack.
                       Ejemplo:
                       {"ls -lstar"},exec, mov(v)
                       ==> guarda la lista de directorio
                       actual en "v".
                      
 V=`línea-comando`     Ejecuta la línea-comando y guarda el
                       resultado en la variable "v".
                       Ejemplo:
                       v = `ls -lstar`
 

OBSERVACION. Una llamada a sistema puede incluir la constante macro E_EXECNULL, con la cual, la llamada se realizará en paralelo a la ejecución del programa. Ejemplo:

  {"ls -lstar ",E_EXECNULL}cat,execv

o bien

  #hl ( execv( cat( "ls -lstar ",E_EXECNULL) ) )

FUNCIONES DE VARIABLES DE SISTEMA

 {V}getenv             Obtiene el valor de la variable de    getenv(V)
                       sistema "V". Ejemplo:
                       {"PATH"}getenv
                       ==> /usr/local/sbin:/usr/local/bin:...
 
 {A,V}setenv           Establece, localmente, una variable   setenv(A,V)
                       de sistema "V" con valor "A".
                       Ejemplo:
                       {"100","CODIGO"}setenv
 
 {V}unsetenv           Elimina la variable de entorno "V".   unsetenv(V)
 
 {A}env?               Devuelve TRUE si A es una variable    isenv(A)
                       de entorno. Ejemplo:
                       {"PATH"}env?
                       ==> 1 (TRUE)

INTERVALOS Y RANGOS DE ARRAYS

HOPPER puede manejar arrays desde una hasta tres dimensiones. No maneja más que esto, por razones prácticas, o decisiones de diseño. Como HOPPER es un pseudo ensamblador, no posee la sintaxis usada por los lenguajes de alto nivel para acceder a los elementos de un array; en su lugar, usa instrucciones. No obstante, se puede acceder a una sintaxis más estándar, para algunas de las operaciones, dentro de #HIGH-LEVEL.

 HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 
 [a,b,c]               Establece un intervalo a operar en
                       las matrices y vectores. Acepta
                       hasta 3 dimensiones, y se pueden
                       definir, de izquierda a derecha,
                       filas, columnas y páginas.
                       Las instrucciones que usan estos
                       intervalos son:
                       GET, PUT, = (asignación).
                     
                       Para acceder a una porción de
                       un array, por ejemplo, de 2D,
                       se suele hacer esto:
 
                       [2:3,4:end]get(array),plus'10',
                       put(array)
                     
                       lo que obtiene una sub-matriz de
                       "array", de 2 filas y 7 columnas,
                       suponiendo que array tiene 10;
                       luego, suma 10 a sus elementos,
                       y vuelve a ponerlo en el array
                       original.
                     
                       En #HIGH-LEVEL, esto se humaniza:
                     
                       #hl{
                          array[2:3,4:end] = array[2:3,4:end]+10
                       }
                       o bien:
                       #hl{
                          array[2:3,4:end] += 10
                       }
                     
                       aunque #HIGH-LEVEL es más sencillo
                       de usar, es más costoso que la
                       versión ensamblada de HOPPER.
                     
                       Ejemplos de intervalos:
                       [10]        fija la posición 10
                                   de un vector cualquiera;
                                   también, fija la coor-
                                   denada 10 de una 
                                   matriz 3D.
                     
                       [10:15]     fija un intervalo que va
                                   desde la posición 10
                                   hasta la 15, dentro
                                   de cualquier vector
                                   que la use.
                     
                       [2,4:10]    fija coordenadas para
                                   cualquier matriz 2D,
                                   fila 2, columnas 4 a
                                   la 10.
                                  
                       [5:end,5]   fija coordenadas para
                                   cualquier matriz 2D,
                                   filas 5 hasta el final,
                                   columna 5.
                                  
                       [3:4,2,2:5] fija coordenadas para
                                   una matriz 3D, filas
                                   3 y 4, columna 2,
                                   páginas 2 a la 5.
 
                       ++i,--j,[1:i,j]
                                   fija coordenadas para
                                   una matriz 2D, con uso
                                   de variables que deben
                                   ser modificadas fuera de
                                   [].
 
                       Nota: SE ACEPTAN variables dentro de
                             los [].
                             
                             ACEPTA INTERVALOS. Ejemplo:
                             
                       [1:2:end]   trabaja sobre las posiciones
                                   impares del array.
                       
                       [1:end, 2:(f+1):end]
                                   fija el trabajo sobre todas
                                   las filas, pero las columnas
                                   desde la 2 hasta el final, a
                                   intervalos (f+1).
                           
                       Nota: una operación aritmética dentro de
                             un intervalo, debe escribirse con
                             paréntesis.
                             
                             ACEPTA operaciones aritméticas
                             dentro de []; sin embargo, no
                             acepta el uso de funciones o
                             macro-funciones.
 
 {N}loc1, loc1(N)     Establece la posición N de un vector
                      cualquiera. Ejemplo:
                        {10}loc1, get(array)
                        loc1(10), get(array)
                      hacen lo mismo. La diferencia, es que
                      con el primero se pueden realizar 
                      operaciones aritméticas:
                        {10}plus'i',loc1.
                      o bien
                        #hl( 10+i ), loc1
                      pero, con LOC1(N), no puede hacerse.
                      N, en este caso, es un numero o una
                      variable.
                     
                      Por otro lado,
                        loc1(10)
                      es similar a:
                        [10]
                     
                      Nota: También se establece el número
                      de página, en una matriz 3D.
                     
 {F,C}loc2            Establece coordenadas para un elemento
                      de una matriz 2D cualquiera, en fila F
                      columna C.
                     
 {N}offset1           Establece la posición final de un
                      intervalo para un vector, iniciado 
                      con LOC1.
                      Ejemplo:
                        {3}loc1,{6}offset1
                      es similar a:
                        [3:6]
                      y
                        {3}loc1,{0}offset1
                      es igual a:
                        [3]
                       
 {F,C}offset2         Establece el desplazamiento en filas F
                      y columnas C, para un intervalo usado en
                      una matriz 2D cualesquiera. 
                      Si no se quiere desplazar alguna 
                      coordenada, use 0.
                      Ejemplo:
                        {3,2}loc2,{end,4}offset2
                      es similar a:
                        [3:end,2:4]
 
 {N}interval1,
 {F,C}interval2
 {F,C,P}interval3     Establecen intervalos para las marcas
                      definidos por LOC1,LOC2, y OFFSET1 y
                      OFFSET2.
                     
                      Se pueden usar junto a la forma simple:
                         [1:10],{2}interval1
                      declara el siguiente rango de posiciones,
                      internamente:
                         1,3,5,7,9
                     
                      El caso es el mismo para INTERVAL2 e
                      INTERVAL3. 
 
 clearmark            Elimina las marcas definidas por LOC1, LOC2,
                      OFFSET1 y OFFSET2.
                      Esto es útil, cuando se desea asignar un
                      valor constante a una matriz.
                      La asignación "=", por ejemplo, a=0
                      Asignará "0" a la matriz "a"; pero, si
                      existen marcas, asignará "0" a las
                      posiciones definidas por las funciones
                      anteriores.
                     
 clearinterval        Esto es necesario para borrar los intervalos
                      definidos con INTERVAL1, INTERVAL2 e
                      INTERVAL3.
                      
 clrmarksall          Activa/desactiva la ejecución de CLEARMARK y
                      CLEARINTERVAL internamente, luego de la
                      ejecución de un GET o un PUT.
 
 {M}cartesian         Genera una matriz o vector con las posiciones
                      cuyo valor es TRUE (o equivalente), de un
                      vector o matriz. 
                      Las funciones lógicas vistas en OPERADORES
                      MATEMATICOS Y LOGICOS, segmento OPERACIONES
                      LOGICAS MATRICIALES, devuelven vectores o
                      matrices booleanas, cuando se comparan
                      esta clase de datos, y pueden ser usadas
                      junto con CARTESIAN. No obstante, cualquier
                      valor distinto de vacío (matrices de cadena),
                      distinto de cero (matrices numéricas), o
                      con 1's lógicos (matrices booleanas), será
                      tomado por CARTESIAN para obtener una
                      posición.
                      Ejemplo:
                        Sea "t" una matriz:
                          {t}gthan(5), ==> devuelve una matriz
                                           del tamaño de "t",
                                           con 1's y 0's lógicos
                                           según el resultado de
                                           la comparación;
                          cartesian    ==> deja en el stack una
                                           matriz con las posiciones
                                           donde encontró 1's.
                                           Ejemplo:
                                             1,1
                                             2,4
                                             2,5...
 
 range(R)             Toma la matriz cartesiana R obtenida de la
                      instrucción CARTESIAN, y establece rangos
                      de operación sobre matrices. No deja nada
                      en el stack.
                      Estos rangos se denominan: 
                         RANGOS CARTESIANOS
                         
                      GET, PUT, y asignación "=" son afectadas
                      por estos rangos especiales.
                      Sirve para modificar elementos determinados
                      dentro de una matriz, que no obedecen a los
                      rangos vistos con las funciones anteriores.
                      Ejemplo:
                         {t}gthan(5), cartesian,mov(r)
                         range(r),get(t),mulby(100), put(t),
                         {t} println
                         
                      Elabora un rango cartesiano con las posiciones
                      mayores que 5, obtiene esos elementos de la
                      forma de un vector (GET), los multiplica por 100,
                      y luego, los colocan nuevamente dentro de la
                      matriz original, con PUT.
                      Después, imprime la matriz modificada.
 
  clearrange           Elimina el rango cartesiano.

ESTADISTICA

La instrucción STATS es del tipo NAVAJA SUIZA: a través de ella es posible acceder a numerosos cálculos estadísticos básicos, con los cuales se pueden relizar operaciones más complejas, por combinación de ellos.

 HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 {V}stats(CODIGO)      Ejecuta una serie de cálculos         no existe
 
 {V}statistic(CODIGO)  estadísticos simples, según el
                       CODIGO dado.

Los códigos están definidos en HOPPER.H.

LISTA DE CODIGOS

SUMMATORY            Efectúa una sumatoria de todos los valores de una matriz.
                     Ejemplo:
                     {V}stats(SUMMATORY)

MEAN                 Calcula el promedio de los valores de la matriz.
                     Ejemplo:
                     {V}stats(MEAN)
                     
SUMM+MEAN            Devuelve un array 1D con la SUMA y el PROMEDIO de todos los
                     valores de una matriz.
                     Ejemplo:
                     {V}stats(SUMM+MEAN)  ==>  { suma, promedio }

BINCLASS             Genera clases, según un bin (intervalos de clase). Devuelve 
                     un array 2D con los valores de los intervalos calculados, y
                     la ocurrencia.
                     Ejemplo:
                     {25,V}stats(BINCLASS)  ==>  {ini,fin,ocurrencia}
                     Genera una "tabla" con 25 bines.

STURGCLASS           Genera intervalos de clase con método de Sturges.
                     Ejemplo:
                     {V}stats(STURGCLASS)

CLASS                Genera una estadística para cada elemento (ocurrencia) de los
                     valores de la matriz.
                     Ejemplo:
                     {V}stats(CLASS)  ==> {Elemento, ocurrencia}

SUMMCOL              Suma columnas de una matriz 2D, y devuelve un array 1D con el
                     total para cada columna.

SUMMROW              Suma filas de una matriz 2D, y devuelve un array 1D con el
                     total para cada fila.

OBSERVACION. SUMMCOL y SUMMROW solo trabajan sobre matrices 2D. Para calcular dicha
suma sobre matrices 3D, es necesario obtener cada página con GETPAGE, y los resultados
pueden ser concatenado con CATROW o CATCOL.

CONJUNTOS

La intrucción SETS es una instrucción tipo NAVAJA SUIZA, con la cual se pueden realizar las operaciones básicas de CONJUNTOS.

 HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 {C}sets(CODIGO)       Ejecuta una serie de cálculos sobre un conjunto C, según un
                       código (ver lista de códigos).
                       Un conjunto se define como una colección ordenada de elementos
                       únicos. Para ordenar los elementos, se usa:
                       {V}array(SORT)
                       La definición para SORT se encuentra en HOPPER.H.

Los códigos están definidos en HOPPER.H.

LISTA DE CODIGOS

UNIQUE               Obtiene un array 1D ordenado con los valores únicos obtenidos
                     desde una matriz.
                     Ejemplo:
                     sea "V" un array con los valores:
                     V: 1,2,3,4,1,3,8,10,9,0,0,2
                     
                     {V},sets(UNIQUE)
                     ==> {0,1,2,3,4,8,9,10}
 
UNION                Efectúa la UNION de dos matrices procesadas previamente por
                     UNIQUE.
                     Ejemplo:
                     {A,B}sets(UNION)
 
INTERSEC             Efectúa la INTERSECCION de dos matrices procesadas previamente
                     por UNIQUE.
                     Ejemplo:
                     {A,B}sets(INTERSEC)
 
DIFF                 Efectúa la DIFERENCIA de dos matrices procesadas previamente
                     por UNIQUE.
                     Ejemplo:
                     A: 1 2 3 5 6 7 8 9 10
                     B: 1 4 6 7 8 9 10
 
                     {A,B}sets(DIFF) ==> {2,3,5}
                     
                     OBSERVACION. {A,B}sets(DIFF) es diferente a {B,A}sets(DIFF)
                     Ejemplo:
                     {B,A}sets(DIFF) ==> {4}
 
SIMDIFF              Efectúa la DIFERENCIA SIMETRICA de dos matrices procesadas 
                     previamente por UNIQUE.
                     Ejemplo:
                     A: 1 2 3 5 6 7 8 9 10
                     B: 1 4 6 7 8 9 10
 
                     {A,B}sets(SIMDIFF) ==> {2,3,4,5}
                     
                     OBSERVACION. {A,B}sets(SIMDIFF) es igual a {B,A}sets(SIMDIFF)

MANEJO DE BITS Y CAMBIO DE BASE

La instrucción BIT es una función tipo NAVAJA SUIZA, que encierra numerosas herramientas para el manejo de bits.

 HOPPER               DESCRIPCION                           ALTO NIVEL              
 -----------------------------------------------------------------------------------
 {N}bit(CODIGO)        Ejecuta una serie de cálculos de bits sobre N números enteros
                       de 16 bits (ver lista de códigos).
                       Si CODIGO es menor que 100, hace referencia a la posición del
                       bit.
                       Ejemplo (con macro FOR/NEXT):
                     
                       num=4003
                       for(i=32, {i}is greater than (0),--i)
                          {num},bit(i), print
                       next
                       ==> 00000000000000000000111110100011
 
 {N}hex                Convierte un número decimal a una cadena hexadecimal.
                       {200}hex  ==>  C8
 
 {N}bin                Convierte un número decimal a una cadena binaria.
                       {200}bin  ==>  11001000
 
 setbin(n)             Establece un número "n" de dígitos binarios para la salida
                       de BIN.
                       setbin(15)
                       {200}bin  ==>  000000011001000
 
 {N}oct                Convierte un número decimal a una cadena octal.
                       {200}oct  ==>  310

Los códigos están definidos en HOPPER.H.

LISTA DE CODIGOS

BITAND               AND binario. Ejemplo:
                     {0xfffh,0x99h},bit(BITAND)  ==>  153 (99h)
 
BITOR                OR binario. Ejemplo:
                     {0xfffh,0x99h},bit(BITOR)   ==>  4095 (FFFh)
 
BITXOR               XOR (OR exclusivo) binario.
                     Ejemplo:
                     {0xfffh,0x80h},bit(BITXOR)  ==>  3967 (F7Fh)
 
BITNOT               NOT binario. Ejemplo:
                     {0xfffh},bit(BITNOT)        ==>  4294963200 (FFFFF000h)
 
BITCLR               Deja el bit indicado en 0 (lo apaga). El valor del TOP
                     es el entero a procesar; el resto, son las posiciones
                     de bits, comenzando desde la posición 1.
                     Ejemplo:
                     num=0xfffh
                     {7,5,4,3,num},bit(BITCLR)   ==>  4003 (111110100011)
 
BITSET               Enciende el bit indicado. El valor del TOP es el entero
                     a procesar; el resto, son las posiciones de bits, 
                     comenzando desde la posición 1.
                     Ejemplo:
                     num=4003
                     {7,5,4,3,num},bit(BITSET)   ==>  4095 (111111111111)
 
MIRRBYTE             Invierte los bits de un entero. LOWBYTE y HIGHBYTE definen
                     los bits a reflejar (si solo el byte más bajo, o todo).
                     Ejemplo:
                     num = 2289 (0000100011110001)
                     {LOWBYTE,num},bit(MIRRBYTE)  ==> 0000100010001111 = 2191
                     {HIGHBYTE,num},bit(MIRRBYTE) ==> 1000111100010000 = 36624
 
BITROT               Rota bits hacia la izquierda. El bit de más hacia la
                     izquierda (del byte más bajo o del más alto), pasa a ser 
                     el primer bit.
                     BITROT distingue LOWBYTE y HIGHBYTE. 
                     Ejemplo 1 (LOWBYTE):
 
                     num = 2289 // (0000100011110001)
                     i=1
                     loop lowbyte:
                        {LOWBYTE,i,num},bit(BITROT),bin,P_NL,print
                        ++i,{5,i} jle(loop lowbyte)
 
                     Salida:
                     
                     0000100011100011
                     0000100011000111
                     0000100010001111
                     0000100000011111
                     0000100000111110
 
                     Ejemplo 2 (HIGHBYTE):
 
                     num = 2289 // (0000100011110001)
                     i=1
                     loop highbyte:
                        {HIGHBYTE,i,num},bit(BITROT),bin,P_NL,print
                        ++i,{5,i} jle(loop highbyte)
 
                     Salida:
                     
                     0001000111100010
                     0010001111000100
                     0100011110001000
                     1000111100010000
                     0001111000100001
                     
SHIFTR
SHIFTL               Desplazan los bits de un entero hacia la izquierda (SHIFTL)
                     como hacia la derecha (SHIFTR). Los bits de los extremos se
                     pierden.
                     Ejemplo:
                     
                     num=25670  // 0110010001000110
                     {5,num},bit(SHIFTR)  ==>  0000001100100010 = 802
                     {5,num},bit(SHIFTL)  ==>  1000100011000000 = 821440
 
HEXTODEC
OCTTODEC
BINTODEC             Convierten una cadena hexadecimal, binaria, o un número octal,
                     en un número decimal computable.
                     Ejemplo:
                     {"\nBINTODEC:", "110101"}bit(BINTODEC), print
                     {"\nHEXTODEC:", "f702a"} bit(HEXTODEC), print
                     {"\nOCTTODEC:", 36173}   bit(OCTTODEC), print
                     
                     Salida:
                     
                     BINTODEC:53
                     HEXTODEC:1011754
                     OCTTODEC:15483
                     
LOWBYTE              Macro que señala el byte más bajo, usado en BITROT y MIRRBYTE.
HIGHBYTE             Macro que señala el byte más alto, usado en BITROT y MIRRBYTE.

ATRAPAMIENTO DE ERRORES

HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
swtrap(DIR)          Instrucción detrás de la macro TRY.
                     Almacena la dirección de salto DIR al
                     punto del programa donde un error
                     será atrapado.

gettry(E)            Instrucción detrás de CATCH. Obtiene
                     el código numérico del error, y lo
                     guarda en la variable E.
                                    
{M}throw(E)          Levanta un error E definido por el
                     usuario, guardando el mensaje M para
                     su posterior proceso. 
                     Su uso normal es:

                     {"Has pasado el límite"},throw(1000)
                     
                     La macro, definida en stdio.hh, tiene
                     un uso más normal:

                     raise(1000,"Has pasado el límite")

popcatch             Elimina la dirección almacenada por
                     SWTRAP, desde el stack interno de
                     direcciones de salto por errores.
                     Está detrás de FINISH, y mejor use
                     esta macro.

OBSERVACIONES: Estas instrucciones no deben ser usadas. En su lugar, use las macros dispuestas en stdio.hh, donde se establece el uso más estándar. Ejemplo:
                    try
                       ... instrucciones con error o con RAISE
                    catch(error)
                       ...alguna operación...
                    finish

assert(M)            Examina el stack, y si encuentra un
                     valor TRUE, termina el programa,
                     lanzando un mensaje M. Ejemplo:
                     
                     {0,v}eq?, assert("Son iguales!")
 

PARSER XML/HTML

HOPPER               DESCRIPCION
-----------------------------------------------------------------------------------
{A,B,C,D}parser(M)    Construye un registro XML/HTML, y lo agrega a M, donde:
                      A = nombre del campo
                      B = atributos
                      C = contenido del campo
                      D = código de parser (ver lista de códigos).
                      M = cadena con registros anidados XML.
                      Si no hay atributos, debe escribir "" en su lugar.
                      Si no hay contenido, debe escribir "" en su lugar.
                      El nombre del campo, y el código de parser, son
                      obligatorios. Obviamente, la cadena con registros
                      anidados también son obligatorios.
 
     Ejemplo:
 
     msg=""
     {"nombre","","Este tag esta con datos",NORMALTAG}, parser(msg)
     {"text_data","name=\"CODE\" val=200","",NORMALTAG},parser(msg)
     {"chupete","","",NORMALTAG},parser(msg)
     {"mem_copy"},
     #compute(cat("length=",xtostr(1000))) 
     {"",ONLYTAG},parser(msg)
     {"code_secret","tag=AB450C M=\"MORE\"","INFO-C",NORMALTAG},parser(msg)
     {"only_tag","","",ONLYTAG},parser(msg)
 
     Genera:
     <nombre>Este tag esta con datos</nombre>
     <text_data name="CODE" val=200></text_data>
     <chupete></chupete>
     <mem_copy length=1000/>
     <code_secret tag=AB450C M="MORE">INFO-C</code_secret>
     <only_tag/>
 
     Para dejar el grupo de registros anterior dentro de otro registro, escribir:
  
     forma=""
     {"forma","code=100001",msg,NORMALTAG},parser(forma)
 
     Genera:
     <forma code=100001>
        <nombre>Este tag esta con datos</nombre>
        <text_data name="CODE" val=200></text_data>
        <chupete></chupete>
        <mem_copy length=1000/>
        <code_secret tag=AB450C M="MORE">INFO-C</code_secret>
        <only_tag/>
     </forma>
 
{A}unparser(M)        Descompone la cadena M, obteniendo el campo indicado
                      por A. Si el campo no existe, devuelve un error que
                      puede ser atrapado por las macros TRY/CATCH.
                      El campo A puede estar en cualquier parte de la
                      cadena M. Si existe más de un campo con el mismo
                      nombre, será extraída la primera ocurrencia.
                     
                      Si A contiene atributos, UNPARSER devuelve:
                      1) un array con los valores de los atributos.
                      2) el contenido.
                      
                      Si A no contiene atributos, solo devuelve el contenido.
                     
                      Si A no contiene contenidos, devuelve una cadena nula.
                     
                      Si se sabe que A no contiene atributos, solo es 
                      necesaria una variable para el contenido.
 
     Ejemplo:
     content=""
     {"nombre"}unparser(forma),mov(content)
     
     Devuelve:
     contenido  ==>  Este tag esta con datos
     
     content="", atr=0
     {"code_secret"}unparser(forma), mov(atr),mov(content)
     
     Devuelve:
     contenido  ==>  INFO-C
     Atributos  ==>  {AB450C, MORE}
     
     {"only_tag"}unparser(forma),mov(content)
     
     Devuelve:
     contenido  ==>  
     
     En MSG, luego de los UNPARSER anteriores, queda:
     
     <forma code=100001>
        <text_data name="CODE" val=200></text_data>
        <chupete></chupete>
        <mem_copy length=1000/>
     </forma>

Los códigos están definidos en HOPPER.H.

LISTA DE CODIGOS

NORMALTAG          Señala un registro con atributos y contenido.
                   Tanto atributos como contenido pueden ser nulos.

ONLYTAG            Señala que el registro será solitario. Puede
                   contener atributos, pero el contenido debe ser
                   nulo.

ARCHIVOS

HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
{F}statsfile         Devuelve al stack la siguiente        statsfile(F)
                     información:
                     
                     {#líneas, 
                      #total de caracteres,
                      longitud línea más larga,
                      #tokens por línea}
                     
                     "#tokens por línea" dependerá de
                     TOKSEP.
                     Ejemplo: sea el siguiente archivo:
                     ARCHIVO.TXT:
                        RX/RY,A,B,C,D,E,F,G,H,I,J
                        fila 1,1,2,3,4,5,6,7.998,8,9.034,10
                        fila 2,10,20,30,40,50,60,70,80,90,100
                        fila 3,100,200,300.5,400,500,600,700,800,900,1000
                        fila 4,5,10,15,20,25,30,35,40,45,50
                        fila 5,a,b,c,d,e,f,g,h,i,j
                        fila 6,1,2,3,4,5,6,7,8,9,10
                     
                        main:
                           s=""
                           {","} tok sep
                           {"archivo.txt"} stats file, join(s)
                           {s,"\n"}print
                        {0}return
                     
                     Imprimirá:
                         7,241,49,11
                     
                     Nota: JOIN se usa para "fusionar" todos los elementos
                     del stack en una variable de cadena, separados por
                     el separador de token definido por TOKSEP.
 
{T,F}open(FD)        Abre un archivo identificado por F, usando un modo de
                     apertura T, y crea el identificador de archivo FD.
                     Si ocurre un error en la apertura, la instrucción
                     ERROR? devuelve TRUE, y la cadena de error es devuelta
                     por FILEERROR.
                     Ejemplo:
                        fd=0
                        {OPEN_READ,"archivo.txt"}  open(fd)
                     
                     Abre el archivo "archivo.txt" en modo de solo lectura,
                     y le asigna el identificador "fd".
                     
                     Nota: los modos de apertura se encuentran en stdio.hh.
                     La lista es la siguiente:
                     
                     OPEN_READ        0     // solo lectura
                     OPEN_WRITE       1     // solo escritura
                     OPEN_APPEND      2     // lectura y escritura (append)
                     OPEN_EXCLUSIVE   16    // RED: solo lectura exclusivo
                     OPEN_DWRITE      32    // RED: inahbita que otro escriba
                     OPEN_DREAD       48    // RED: inhabilita que otro lea
                     OPEN_SHARED      64    // RED: comparte lectura y escritura
 
{T,F}create(FD)      Crea un archivo identificado por F, usando un modo de
                     creación T, y asigna el identificador FD.
                     Si ocurre un error en la creación, la instrucción
                     ERROR? devuelve TRUE, y la cadena de error es devuelta
                     por FILEERROR.
                     Ejemplo:
                        fw=0
                        {CREATE_NORMAL,"archivo.txt"} create(fw)
                     
                     Nota: los modos de creación se encuentran en stdio.hh.
                     La lista es la siguiente:
 
                     CREATE_NORMAL    0
                     CREATE_READONLY  1
                     CREATE_HIDDEN    2
                     CREATE_SYSTEM    4
 
close(FD)            Cierra un archivo abierto con OPEN, o creado con CREATE.
                     Ejemplo:
                        close(fd)
 
eof(FD)              Devuelve TRUE si el handler FD ha llegado al final del
                     archivo.
                     
                     NOTA: si el último caracter del archivo es una secuencia
                     de escape, y está usando READLINE, READSTRING, o una
                     instrucción parecida, EOF fallará, y la lectura continuará.
                     Para remediar esto, lleve a cabo cualquiera de las siguientes
                     indicaciones:
                     1) elimine el último caracter si es una secuencia de escape.
                     2) use STATFILE, obtenga el total de líneas, y léalas usando
                        un contador de líneas.
                     3) use SEARCH,y lea cada línea usando GET sobre el resultado
                        de SEARCH, y luego SEEK para posicionar el puntero del
                        archivo, antes de leer la línea.
                     Los puntos 2 y 3 pueden ser lentos para archivos muy
                     grandes, además de costoso, pero es seguro. La lentitud
                     proviene del proceso de recolección de los datos del archivo,
                     que realizan tanto SEARCH como STATFILE.
 
{T,P}seek(FD)        Establece el puntero de posición del archivo apuntado por FD. 
                     La nueva posición, medida en bytes, es obtenida desplazando
                     P bytes a la posición especificada por T.
                     Si T es configurada por SEEK_SET, el desplazamiento es
                     relativo al inicio del archivo;
                     Si T es SEEK_CUR, el desplazamiento es relativo a la posición
                     actual del puntero;
                     Si T es SEEK_END, el desplazamiento es al fin del archivo.
                     SEEK devuelve la nueva posición al stack. Si no desea usar
                     este dato, puede eliminarlo con KILL.
                     Ejemplos:
                        {SEEK_CUR,0} seek(fd)
                     
                     Obtiene la posición actual del puntero del archivo, luego 
                     de alguna operación.
                        
                        {SEEK_SET,100},seek(fd)
                     
                     Se mueve a la posición 100 del archivo, contando desde el
                     principio de dicho archivo.
 
                        {SEEK_END,0},seek(fd), mov(nFinalPos)
                     
                     Se mueve a la última posición del archivo. Se puede saber
                     la longitud del archivo, en bytes.
                     
                        {SEEK_SET,0},seek(fd), mov(nIniPos)
                     
                     Se mueve al inicio del archivo.
                     
                     Las constantes para T están en stdio.hh, y se definen a
                     continuación:
                     
                     SEEK_SET         0
                     SEEK_CUR         1
                     SEEK_END         2
 
{max-long}readline(FD)
                     Lee una línea desde el archivo apuntado por FD,
                     y la deja en el stack. La línea obtenida es una
                     cadena.
                     Si ocurre un error, la instrucción ERROR? 
                     devuelve TRUE, y la cadena de error es devuelta
                     por FILEERROR.
                     Ejemplo:
                        {OPEN_READ,"archivo.txt"} open(fd)
                        loop:
                           {250} read line(fd)
                           {"\n"}, print
                           eof(fd), jnt(loop)
                           close(fd)
                     
                     Nota: esta instrucción mueve el puntero del archivo
                     al inicio de la línea siguiente de la leída, o al
                     fin de archivo.
 
{S}writeline(FD)     Guarda una cadena en el archivo apuntado por FD.
                     Si ocurre un error, la instrucción ERROR? 
                     devuelve TRUE, y la cadena de error es devuelta
                     por FILEERROR.
                     Ejemplo:
                        {CREATE_NORMAL,"mensaje.txt"} create(fw)
                        {"Esta línea será guardada"},write line(fw)
                        close(fw)
 
{B}readstring(FD)    Lee un número de B bytes desde un archivo apuntado
                     por FD, y lo deja en stack como una cadena.
                     Ejemplo:
                        {OPEN_APPEND,"texto.txt"} open(fd)
                        {10} read string(fd)
                     
                     Lee 10 bytes desde el archivo FD.
                     
                     Nota: esta instrucción se combina fuertemente con
                     la instrucción SEEK.
 
{S}writestring(FD)   Guarda una cadena S en el archivo apuntado por FD.
                     Ejemplo:
                        {OPEN_WRITE,"texto.txt"} open(fd)
                        {"MENSAJE PARA LA TIERRA\n"} write string(fd) 
 
                     Nota: esta instrucción se combina fuertemente con
                     la instrucción SEEK.
 
{F}loadstring(S)     Lee el archivo F y lo guarda en la cadena S.
                     Ejemplo:
                        s = "" 
                        {"texto.txt"},load string(s)
 
{F}savestring(S)     Guarda la cadena S en el archivo F.
                     Ejemplo:
                        s = "Mensaje a guardar"
                        {"texto.txt"} save string(s)
 
[L]getline(S)
{L}getline(S)        Obtiene la línea L desde la cadena S. Se considera
                     una línea hasta donde se encuentra un salto de
                     línea.
                     La línea puede ser obtenida desde el stack, como
                     también, desde una marca [].
                     Ejemplo:
                       // v es una cadena con saltos de línea
                       i=1
                       loop:
                         {"\nLinea ",i," = ",i},get line (v),print
                         ++i
                         {c,i},jle(loop)
 
countlines(S)        Cuenta el total de líneas de la cadena S, y
                     guarda el resultado en el stack. 
 
{B,F}search(S)       Busca una cadena, o una expresión regular (B) en el
                     archivo F, elabora una matriz con las posiciones
                     encontradas. Esta matriz es bidimensional, y cada
                     fila contiene:
                        {# de linea, desplazamiento}
                     
                     # de línea:
                     SEARCH se empalma con READLINE, para un mejor
                     resultado, y con SEEK y GET, para ir directo a la
                     línea en cuestión.
                     
                     desplazamiento:
                     SEARCH se empalma con SEEK.
                     
                     Internamente, SEARCH usa GREP, el buscador por
                     excelencia de Linux. Por eso la búsqueda es 
                     rápida, no se vaya a pensar que el mérito es
                     mío.
                     
                     Si va a usar una expresión regular como patrón de
                     busqueda, añada la opción "-e" dentro de la cadena
                     del patrón. Grep.
 
                     Para un ejemplo completo, ver el archivo de ejemplo
                     GREP.COM del directorio SRC.
                     Ejemplo:
                        main:
                                                          v=0,fd=0
                           {"-e \"\#[^RND]\"","stdio.hh"} search(v)
                           {OPEN_READ,"stdio.hh"}         open(fd)
                                                          [10,2] get(v),
                                                          mov(nSavePos)
                           {SEEK_SET,nSavePos}            seek(fd), kill
                           {1000}                         read line(fd)
                           {"\n"}                         print
                                                          close(fd)
                                                          clear(v)
                        {0}return
                     
                     Busca todas las líneas que tengan "#" y que continúe
                     con cualquier cosa, menos "R" ni "N" ni "D", en el
                     archivo HOPPER.H. Guarda las referencias en el array
                     "v". Luego, se obtiene la fila 10, el valor del
                     desplazamiento (posición 2 de "v") con GET, y se usa
                     para desplazar el puntero del archivo con SEEK.
                     (Se mata lo que devuelve GET al stack, porque no
                     se usará, con KILL).
                     A continuación, se lee la línea completa con READLINE
                     se imprime, cierra y limpia todo, y fin.
                     
                     Nota: el estilo de codificación es novedoso: por un
                     lado, se ubican los accesos al stack; por otro, las
                     instrucciones.
 
fileerror            Devuelve una cadena con el mensaje de error de
                     la operación de archivo fallida.

PILAS, COLAS Y RECURSIVIDAD

En HOPPER se pueden usar los arreglos como recipientes para simular PILAS, COLAS, y también, para simular la RECURSIVIDAD. Para ello, existen instrucciones básicas que facilitan el proceso, descritas a continuación.

Ejemplo de uso de PILAS:

#include <hopper.h>
main:
  stack={}                      // se define un array vacío
  {100}push(stack)              // mete el primer dato: 100
  {"Maria tenia un corderito"}push(stack) // el segundo: una cadena
  true, push(stack)             // el trcero, un valor booleano
  arr=-1,{10,10} rand array(arr), push(stack)  /* el cuarto: un array creado en
                                                  el stack (Ver ARRAYS)*/
 /* Ahora, saca los datos del stack */ 
  while( not ( empty(stack) ) )
     pop(stack), printnl     // saca desde el último, hasta el primero
  wend

Ejemplo de uso de COLAS:

#include <stdio.hh>
main:
  stack={}                      // se define un array vacío
  {100}push(stack)              // mete el primer dato: 100
  {"Maria tenia un corderito"}push(stack) // el segundo: una cadena
  true, push(stack)             // el trcero, un valor booleano
  arr=-1,{10,10} rand array(arr), push(stack)  /* el cuarto: un array creado en
                                                  el stack (Ver ARRAYS)*/
 /* Ahora, saca los datos del stack */ 
  while( not ( empty(stack) ) )
     qpop(stack), printnl   // saca desde el primero, hasta el último
  wend

Ejemplo de RECURSIVIDAD:

#include   <stdio.hh>
#proto     factorial(_v_)  /* simula "funciones", expandiendo
                             la llamada "_factorial()", y la
                             declaración "factorial()" */
main:
  arg=100
  {"Factorial de ",arg," = "}print
  _factorial(arg)   // esto expande a "{arg}jsub(factorial)"
  println
{0}return
  
.locals
factorial(n)        // esto expande a "factorial:,n=0,mov(n)"
  if ( {n} lethan '1' )
     {1}
  else
     {n}, _factorial( {n}minus'1' ), mul
  end if
back 
HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
{D}push(S)           Mete un dato D en el stack S. El
                     dato puede ser una constante, un
                     registro-variable, o una matriz.

{D}pushall(S)        mete todo lo que encuentre en el
                     stack, en el array S, en el orden
                     en que estos fueron metidos.
                     Es una alternativa económica a
                     ADDROW cuando se quiere crear un
                     único vector.

pop(S)               Extrae un dato desde el stack,
                     en modo PILA (desde el último,
                     hasta el primero).
                     El dato extraído queda en el stack.

qpop(S)              Extrae un dato desde el principio
                     del stack S, o sea, en modo COLA.
                     El dato extraído queda en el stack.

head(S)              Devuelve, sin extraer, el dato 
                     ubicado al inicio del stack, es
                     decir, el primero ingresado.

tail(S)              Devuelve, sin extraer, el dato
                     ubicado en la cima del stack, es
                     decir, el último ingresado.

empty(S)             Devuelve TRUE si el stack S está
                     vacío.

ipush(V)
ipop(V)              "internal push", e "internal pop".
                     IPUSH es una instrucción que mete V
                     (estrictamente una variable) en una PILA
                     interna usada para llamadas a bloques
                     simulando "funciones".
                     IPOP extrae el valor desde la PILA
                     interna, y lo guarda en la variable
                     indicada como su argumento.
                     
                     Estas instrucciones son usadas de
                     manera interna por HOPPER, cuando se
                     simulan "funciones" con #PROTO 
                     (como en el ejemplo de la recursividad):
                     en este caso, IPUSH e IPOP son invisibles
                     a la expansión PPO, porque son añadidas
                     por HOPPER al momento de realizar la
                     compilación.
                     Asimismo, se puede usar de manera manual,
                     como en el ejemplo a continuación.
                     Por Ejemplo:
                     
                     main:
                        m=0,{10,10}rand array(m)
                        y=10
                        ipush(m)
                        ipush(y)
                        jsub(foo)
                        {0}return
                     .locals
                     foo:
                        m=0,y=0          // se declaran "locales"
                        ipop(y)          // se extraen en orden
                        ipop(m)          // inverso a como fueron
                                         // metidos.
                        {"M=\n",m,"\nY=",y,"\n"} print
                     back

itop(V)              "internal top". Obtiene el dato que está en
                     el "top" de la pila interna. No extrae el dato.
                     Se puede usar en conjunto con IPUSH e IPOP,
                     para aplicaciones del usuario. No obstante,
                     se debe tener cuidado al usarse, porque podría
                     entrar en conflicto con la declaración y uso
                     de pseudofunciones, dado que es allí donde se
                     usa la pila interna.

        IMPORTANTE:  la pila interna tiene un máximo no configurable
                     de 1.024 posiciones. Es, en realidad, un array
                     de objetos, los que se van liberando en la
                     medida de ser ejecutado un IPOP, sin alterar
                     el tamaño de la pila.

ARRAYS

Un array en HOPPER puede ser inicializado vacío, con la siguiente instrucción:

 array = {}
 

Y listo!

No obstante, aún hay otras instrucciones que permiten una inicialización más específica, y operaciones sobre arrays. Las funciones XARRAY(A) crean arrays con dimensiones extraídas desde el stack, sobre la variable A. Dicha variable A debe ser definida previamente como 0, es decir, por ejemplo:

   A=0
   {5,2}zeros array(A)

crea un array A con 5 filas y 2 columnas, lleno de 0.

También puede querer crear un array, y operar sobre él antes de guardarlo en una variable. Esto es sano, considerando que un array ocupa memoria, se crean copias en el stack, y el garbage collector es un mito aún. Para hacer esto, basta con declarar la variable A como -1. POr ejemplo:

   A=-1
   {5,2}rand array(A), mul by '10', ceil, mov(A)
   

se crea un array de números aleatorios que es dejado en el stack; luego, se multiplica por 10, y se aplica función techo (CEIL). Recién ahí es guardado en A. Con A=-1, las funciones XARRAY() saben que el array creado debe guardarse en el stack, y no en la variable pasada por argumento. La versión equivalente a lo anterior:

   A=0
   {5,2}rand array(A), {A},mul by '10', ceil, mov(A)
   

requiere que A sea guardado en el stack, para poder ser operado. A=-1 evita este paso.

HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
{datos}addrow(A)     Añade lo que encuentre en el stack,
                     como una fila en el array A, declarado
                     previamente como un array vacío.
                     El array resultante A es un vector
                     columna de tamaño (1,n).
                     Ejemplo:
                        a={}
                        {1,2,3,4,5,6} addrow(a)
                        {"a","b","c"}true,false,{10} addrow(a)
                     
                     rellena el array "a" con dos filas de
                     datos, y seis columnas. En este caso,
                     "a" es un array MULTITIPO.
                     
                     La primera fila añadida determinará el
                     total de columnas que tendrá el nuevo
                     array. Si se cambia esto después, la
                     instrucción ADDROW dará error.
                     
                     Si se desea crear un vector simple, 
                     bastará con añadir RESHAPE:
                        a={}
                        {1,2,3,4,5,6} addrow(a)
                        {0}reshape(a)
                     
                     "{0}" señala que el array resultante 
                     tendrá dimensión simple.
                     
                     Nota: ADDROW obtiene los datos desde
                     el stack. Esto significa que el numero
                     de datos a añadir, estará restringido
                     al tamaño del stack. Para asegurarse
                     de que el stack puede contener todos
                     los datos que requiere para añadir una
                     fila al array con ADDROW, use al
                     inicio .STACK n, donde "n" es el tamaño
                     que se reservará para el stack.

{F,[C[,P]]}newarray(A)
                     Crea un array A con las dimensiones
                     encontradas en el stack, y en el orden
                     previsto, con posiciones nulas.
                     Sirve para asignar valores al array,
                     posteriormente. Ejemplo:
                       A=0
                       {10,5}new array(A), A=10
                     Creará un array con posiciones nulas,
                     las que serán rellenadas luego con el
                     valor "10".

{F,[C[,P]],dato-relleno}fillarray(A)
                     Crea un array A con un valor dado, sea
                     este cadena, número o booleano.
                     No puede crear un array de arrays. Para
                     eso, vea PILAS, COLAS Y RECURSIVIDAD.
                     Ejemplo:
                       A=0
                       {10,5,"cadena"} fill array(A)
                     Crea un array A de 10 filas y 5
                     columnas, y rellena cada posición con
                     la cadena "cadena".

{F,[C[,P]]}nanarray(A)
                     Crea un array de valores NaN (not a
                     number).
                     Esto es útil para ciertas operaciones
                     donde queremos estar seguros de qué
                     elementos del array son alterados.

{F,[C[,P]]}randarray(A)
                     Crea un array de números aleatorios,
                     que van desde 0 hasta 1.
                     Ejemplo:
                       A=0
                       {10,5}rand array(A)
 
{F,[C[,P]]}zerosarray(A)
                     Crea u array con ceros.
 
{F,[C[,P]]}onesarray(A)
                     Crea un array con unos.
 
{F,[C[,P]]}eyesarray(A)
                     Crea una matriz identidad. Solo se aceptan
                     dos dimensiones, y la matriz puede ser
                     no cuadrada.
                     Ejemplos:
                        A=0, {3,3}eyes array(A)
                     crea:
                        1 0 0
                        0 1 0
                        0 0 1
                     
                        A=0, {3,5}eyes array(A)
                     crea:
                        1 0 0 0 0
                        0 1 0 0 0
                        0 0 1 0 0

MANIPULACION DE ARRAYS

get(A)               Obtiene una porción desde la matriz A, de
                     acuerdo a las marcas de intervalos, o rangos
                     cartesianos, previamente definidos.
                     Ejemplo:
                        [2:3,1:end]get(A)
                     obtiene una matriz de 2 filas (filas 2 y 3),
                     y todas las columnas de la matriz A original.
 
                     La porción obtenida de la matriz, quedará en
                     el stack.
                     
                     Sobre esta porción, se pueden efectuar las
                     operaciones que desee.
 
put(A)               Guarda lo que encuentre en el stack, en la
                     matriz A, de acuerdo a las marcas de intervalos
                     y rangos definidos previamente.
                     Ejemplo:
                        [2:3,1:end]get(A), mul by '10', plus 'tasa'
                        put(A)
                     La porción obtenida, será multiplicada por 10,
                     se le sumará la variable "tasa", y luego, se
                     guardará en A, en sus posiciones originales.
                     
                     Nota: si borra las marcas antes de PUT, habrá
                     un error.
                     
                     El ejemplo anterior, tiene su simil en un
                     lenguaje de programación normal, como sigue:
                        
                        a[2:3,1:end] = a[2:3,1:end] * 10 + tasa
                     
                     Aunque, lo anterior, lo puede hacer #HIGH-LEVEL.
                     Ejemplo:
                     
                     #hl{
                        a[2:3,1:end] *= 10 + tasa
                     }
 
{lista-filas}getrow(A)
                     Creará una nueva matriz con las filas definidas
                     en el stack, y la guardará en el stack. 
                     Ejemplo:
                        {1,3,4,8} get row(A)
                     
                     Crará una matriz con las filas 1, 3, 4 y 8 de A.
                     
                     Puede repetir filas. Ejemplo:
                        {1,3,3,4,8} get row(A), mov(TMP)
                     Esto creará una matriz con la fila 3 repetida, y
                     la guardará en TMP.
                     
                     Esta operación no tiene reversa, como GET/PUT.
 
{lista-columnas}getcol(A) 
{lista-columnas}getcolumn(A)
                     Crea una nueva matriz con las columnas definidas
                     en el stack, y la guardará en el stack.
                     Ejemplo:
                        {1,3,4,8} get col(A)
                     
                     Crará una matriz con las columnas 1, 3, 4 y 8 de A.
                     
                     Puede repetir columnas. Ejemplo:
                        {1,3,3,4,8} get col(A), mov(TMP)
                     Esto creará una matriz con la columna 3 repetida, y
                     la guardará en TMP.
 
                     Esta operación no tiene reversa, como GET/PUT.
                     
                     Nota: con una combinación de GETROW y GETCOL, se pueden
                     obtener sub-matrices con intervalos de la matriz
                     original.
 
{D}catrow(A)         Concatena todos los vectores dentro del stack, al array
                     A, como nuevas filas.
                     Necesariamente, esto se limita a arrays de 1 y 2 
                     dimensiones. No se pueden concatenar arrays 3D.
                     Los vectores deben tener mismo número de elementos
                     que número de columnas de A.
                     Ejemplo:
                     
                     #include <hopper.h>
                     main:
                        t=-1,{5,5} rand array(t),mulby(10),ceil,mov(t)
                        {"T=\n",t}println
                        s={}
                        {100,200,300,400,500}addrow(s),{0}reshape(s)
                        {t,s,s,t}catrow(t)
                        {"T=\n",t}println
                     {0}return
 
                     Nota: como se puede apreciar, se pueden repetir los
                     vectores que serán concatenados. Asimismo, es posible
                     concatenar matrices con dimensiones compatibles,
                     cuidando de que éstas cumplan con el rango.
                     "{0}reshape(s)" convierte "s" en un vector simple,
                     porque ADDROW crea un vector columna de tamaño (1,5).
 
{D}catcol(A), 
{D}catcolumn(A)      Concatena todos los vectores dentro del stack, al array
                     A, como nuevas columnas.
                     Necesariamente, esto se limita a arrays de 1 y 2 
                     dimensiones. No se pueden concatenar arrays 3D.
                     Los vectores deben tener mismo número de elementos
                     que número de filas de A.
                     Ejemplo:

                     #include <stdio.hh>
                     #include <array.hh>
                     main:
                        t=-1,{5,7} rand array(t),mulby(10),ceil,mov(t)
                        {"T=\n",t}println
                        s={}
                        {100,200,300,400,500,600,700}addrow(s),{0}reshape(s)
                        {t,s,s,t}catcol(t)
                        {"T=\n",t}println
                     {0}return

[P]getpage(A)
[P]putpage(A)        GETPAGE obtiene una página de la matriz 3D A, y la
                     deja en el stack.
                     PUTPAGE, toma la matriz 2D del stack, y lo deja en
                     la matriz A, en la página señalada por P.
                     Ejemplo:
                        #include <stdio.hh>
                        main:
                          t=-1,{3,4,4} rand array(t), mulby'10',ceil,mov(t)
                          {"T=\n",t}print
                          [2] get page(t), mulby'100',putpage(t)
                          {"T=\n",t}print
                        {0}return
                     
                     Notas: GETPAGE y PUTPAGE son sensibles a [P]. Puede
                     cambiar P, pero debe estar dentro del rango de páginas
                     existentes en A: no puede añadir nuevas páginas.
                     Para añadir páginas donde quiera, puede crear una macro
                     que combine RESHAPE con funciones ARRAY(SIZE) y 
                     ARRAY(INSERT), y nuevamente RESHAPE. Esto es rápido.
                     Si P cambia entre GETPAGE y PUTPAGE, la página aludida
                     será reemplazada por la página procesada, y la página
                     original se mantedrá intacta.
                     PUEDE OMITIR GETPAGE Y PUTPAGE, usando marcas de
                     INTERVALOS en sus operaciones. Ver INTERVALOS Y RANGOS
                     para más información.
 
size(A)              Devuelve un array con la metadata del arreglo A. Dicho
                     array es guardado en el stack. 
                     El array resultante se compone de los siguientes datos:
                        {#DIMENSIONES,#FILAS,#COLUMNAS,#PAGINAS}
                     
                     Nota:
                     Si alguna dimensión falta, se omite su información en
                     el array resultante. Por ejemplo, si el array es 2D,
                     y tiene 4 filas y 7 columnas, SIZE(A) devolverá:
                        {2,4,7}

empty(A)
{A}isempty           Devuelven TRUE si el array A está vacío. La instrucción
                     ISEMPTY es exactamente igual a EMPTY, solo que obtiene
                     el array desde el stack, y se puede usar en #HIGH-LEVEL
                     como:
                        isempty(array)

EMPAQUETAMIENTO

{S}packarray(A)      Empaqueta, o convierte, la cadena S en un array A, según
                     el token definido en TOKSEP. Si en TOKSEP se define un
                     string vacío:
                        {""}toksep
                     Entonces, solo obtendrá un array con un elemento.
                     Ejemplo:
                        #include <hopper.h>
                        main:
                           s="juanito juega a la pelota"
                           {" "}toksep
                           t={}
                           {s}pack array(t)
                           {","}toksep
                           {"ARRAY:\n",t}println
                          /* ARRAY:
                             juanito,juega,a,la,pelota */
                           {"SIZE de ARRAY = "}size(t),println
                          // SIZE de ARRAY = 1,5
                        {0}return

{A}packstring(S)     Empaqueta, o convierte, el array A en una cadena S,
                     y concatena los elementos según el token definido en
                     TOKSEP. Si en TOKSEP se define un string vacío:
                        {""}toksep
                     Entonces, los elementos se concatenarán juntos.
                     Ejemplo:
                        #include <hopper.h>
                        main:
                           s={}
                           {"juanito","juega","a","la","pelota"}addrow(s)
                           {0}reshape(s)
                           {"."}toksep
                           t=""
                           {s}pack string(t)
                           {"STRING:\n",t}println  
                          /* STRING: 
                             juanito.juega.a.la.pelota */
                           {","}toksep
                           {"SIZE de STRING = "}size(t),println
                          /* SIZE de STRING = 0,26 */
                        {0}return
                     
                     Nota: PACKSTRING no puede empaquetar elementos que
                     no sean cadenas. Si el array contiene elementos de
                     distinto tipo, antes debe usar la función XTOSTR
                     sobre el array, y luego, empaquetar.
                     PACKSTRING no puede empaquetar elementos array.

FUNCIONES UTILES PARA MANEJO DE ARRAYS

{D}reshape(A)        Cambia la forma de una matriz A, y también 
                     puede redimensionarla.
                     "D" son las dimensiones de la nueva
                     matriz, y tiene que tener el mismo
                     número de elementos que la matriz A.
                     Por ejemplo, si A es un vector de largo
                     100, y se quiere reformar a una matriz
                     2D con 25 filas y 4 columnas, está bien,
                     porque 25 x 4 = 100 elementos.
                     Ejemplo:
                        {25,4}reshape(A)
                     
                     Nota: Si D=0, o sea:
                        {0}reshape(A)
                     significa que A será un vector simple.

{A}compact           Compacta un array n-dimensional en    compact(A)
                     un array unidimensional, eliminando
                     los elementos "nulos":
                     ceros, vacíos y FALSE.
                     Acepta un array multitipo.
                     Ejemplo:
                        #include <stdio.hh>
                        main:
                          a={}
                          {"hola","",0,100},true, addrow(a)
                          {0,200,"mundo!"}false,{"loco"},addrow(a)
                          {a} compact,println
                        {0}return
                     
                     imprime:
                        hola, 100, 1, 200, mundo!, loco
                     
                     Nota: en array.hh existen dos macros que 
                     pueden facilitar las cosas: sus nombres 
                     son COMPACTSTRING y COMPACTNUMERIC.
 
{inicio,final,num-elementos}sequencespaced(V)
{inicio,final,num-elementos}seqsp(V)
                     Ambas hacen lo mismo: generan una secuencia de
                     números equiespaciada (respetando el valor final),
                     obteniendo los argumentos desde el stack, y 
                     guardando el array de números en V.
                     Ejemplo:
                        #include <stdio.hh>
                        main:
                           x={}
                           {1,0.5,10},sequence spaced(x)
                           {"Secuencia = \n",x}println
                        {0}return
 
                     Imprime:
   1 0.944444 0.888889 0.833333 0.777778 0.722222 0.666667 0.611111 0.555556 0.5
                     
{inicio,incremento,num-elementos}sequence(V)
{inicio,incremento,num-elementos}seq(V)
                     Ambas hacen lo mismo: generan una secuencia de
                     números, siendo NUM-ELEMENTOS el total de números
                     pedidos. Obtiene los argumentos desde el stack, y
                     guarda el array de números en V.
                     Ejemplo:
                        #include <stdio.hh>
                        main:
                           x={}
                           {1,0.5,10},sequence(x)
                           {","}toksep
                           {"Secuencia = \n",x}println
                        {0}return
 
                     Imprime:
                     1,1.5,2,2.5,3,3.5,4,4.5,5,5.5
 
{I,F}clamp(V)         Limita un escalar o matriz V a un intervalo definido
                      por dos valores, I-nicial, y F-inal.
                      Si I <= V <= F, retorna V al stack.
                      Si V < I, retorna I.
                      Si V > F, retorna F.
                      Para el caso en que V es una matriz, retorna dichos
                      valores para cada posición de ésta.
                      
                      Ejemplo:
                      #include <stdio.hh>
                      main:
                       v=5, jsub(evalúa clamp)
                       v=-1, jsub(evalúa clamp)
                       v=0.99, jsub(evalúa clamp)
                       v=15, jsub(evalúa clamp)
                       v=8, jsub(evalúa clamp)
                       v=1, jsub(evalúa clamp)
                      {0}return
                      .locals
                      evalúa clamp:
                         {1,10} clamp(v), 
                         {"Valor devuelto: ",v,"\n"}print
                      back
                     
                     Imprime:
                     Valor devuelto: 5
                     Valor devuelto: 1
                     Valor devuelto: 1
                     Valor devuelto: 10
                     Valor devuelto: 8
                     Valor devuelto: 1

ARCHIVOS DE MATRICES

{datos-statsfile,F}load
                     Carga un archivo en una matriz, la
                     que es dejada en el stack.
                     Esta instrucción depende de la
                     instrucción STATSFILE, que devuelve
                     estadísticas del archivo, por lo que
                     es necesario ejecutarla justo antes
                     de ejecutar LOAD.
                     Ejemplo: sea el siguiente archivo:
                     ARCHIVO.TXT:
                        RX/RY,A,B,C,D,E,F,G,H,I,J
                        fila 1,1,2,3,4,5,6,7.998,8,9.034,10
                        fila 2,10,20,30,40,50,60,70,80,90,100
                        fila 3,100,200,300.5,400,500,600,700,800,900,1000
                        fila 4,5,10,15,20,25,30,35,40,45,50
                        fila 5,a,b,c,d,e,f,g,h,i,j
                        fila 6,1,2,3,4,5,6,7,8,9,10
                     
                        #include <stdio.hh>
                        main:
                           file="archivo.txt"
                           {","}toksep
                           matriz=0,tipo=0
                           {file},stats file   /* para stats file,
                                es necesario definir antes toksep */
                           {file},load, mov(matriz)
                           {matriz},type,mov(tipo), 
                           println("TIPO=",tipo)
                           print("SIZE="),size(matriz),println
                        return(0)
  
                     Imprimirá:
                        TIPO=<string>
                        SIZE=2,7,11
                     
                     En este caso, la matriz obtenida será del
                     tipo MULTITIPO, porque posee tanto datos
                     numéricos, como alfanuméricos.
  
                     Se pueden cargar segmentos del archivo
                     señalando marcas de intervalos con [],
                     o con las instrucciones similares. Para
                     más información sobre éstas, ver INTERVALOS
                     Y RANGOS.
                     Ejemplo: a continuación, solo se cargará un
                     segmento del archivo.
  
                        #include <stdio.hh>
                        main:
                           file="archivo.txt"
                           {","}toksep
                           matriz=0,tipo=0
                           {file},stats file 
                        /* definiremos marcas a cargar */
                           [2:end,2:end]  /* solo obtenemos los
                                datos numericos, sin encabezados */
                           {file},load,!type,mov(tipo),mov(matriz)
                           println("MATRIZ=\n",matriz,"\nTIPO=",tipo)
                           print("SIZE="),size(matriz),println
                        return(0)
  
                     Imprimirá:
                        MATRIZ=
                        1,2,3,4,5,6,7.998,8,9.034,10
                        10,20,30,40,50,60,70,80,90,100
                        100,200,300.5,400,500,600,700,800,900,1000
                        5,10,15,20,25,30,35,40,45,50
                        a,b,c,d,e,f,g,h,i,j
                        1,2,3,4,5,6,7,8,9,10
  
                        TIPO=<number>
                        SIZE=2,6,10
                     
{M,F}save            Guarda la matriz M en el archivo F.
                     Ejemplo:
                        {matriz,"datos.txt"} save
 
{max-long}readrow(FD)
                     Lee una línea del archivo abierto con el
                     identificador FD, y la guarda en un array
                     de cadenas. MAX-LONG es la máxima
                     longitud de línea, detectada en el archivo
                     con STATSFILE.
                     Esta función depende de TOKSEP.
                     Internamente, se lee una línea; el puntero
                     del archivo es movido a la posición 
                     directamente posterior a la última posición
                     leída. La línea leída, se separa en sus
                     tokens, y se rellena el array resultante.
                     Ejemplo:
                        #include <stdio.hh>
                        main:
                           fd=0
                           {OPEN_READ,"archivo.txt"} open(fd) 
                           error? do{
                                 {"Error de archivo:"}
                                 file error, println
                                 jmp(salir)
                           }
                           max Long = 1000  // máximo arbitrario
                           linea=""
                           {","}toksep
                           loop:
                              {max Long},read row(fd)
                              mov(linea)
                              {"Posicion final de linea leida: "}
                              {SEEK_CUR,0} seek(fd)
                              {"\n",linea},println
                              eof(fd), jnt(loop)
                              close(fd)
                        salir:
                           {0}return
                     
                     Imprimirá:
                        Posicion final de linea leida: 26
                        RX/RY,A,B,C,D,E,F,G,H,I,J
                        Posicion final de linea leida: 62
                        fila 1,1,2,3,4,5,6,7.998,8,9.034,10
                        ...
                        Posicion final de linea leida: 241
                        fila 6,1,2,3,4,5,6,7,8,9,10
 
{A}writerow(FD)      Escribe los elementos de una fila del array A
                     en el archivo identificado por FD. El array
                     puede ser MULTITIPO.
                     Ejemplo: el siguiente programa obtiene cada
                     fila del array "a", y la guarda en el archivo
                     apuntado por "fw2:
  
                        #include <stdio.hh>
                        main:
                           a={}
                           {"Este","array","sera","guardada",\
                            "en","un","archivo"}
                           !(7),addrow(a)
                           !(7),addrow(a)
                           addrow(a)
                           fw=0
                           {CREATE_NORMAL,"copy_archivo.txt"}
                           create(fw)
                           i=1
                           loop:
                              [i,1:end],get(a),write row(fw)
                              ++i,{i}lethan '3',jt(loop)
                              close(fw)
                           {0}return

FUNCION ARRAY

{A}array(CODIGO)

La instrucción ARRAY, del tipo NAVAJA SUIZA, altera el array A target. No sirve dejar una copia del array en el stack, usando "@", porque el resultado se perderá. Esta instrucción solo trabaja con arrays de 1 DIMENSION. Para trabajar con arrays de más dimensiones, puede aplicar RESHAPE: "{0}reshape(array)", y operar. En tal sentido, las aplicaciones son varias: por ejemplo, se pueden insertar páginas en matrices 3D, o eliminarlas, cosa que no se puede hacer con las instrucciones que operan con matrices vistas hasta ahora.

LISTA DE CODIGOS

SORT              Ordena los elementos de un array 1D. El array puede
                  ser MULTITIPO; no obstante, buenos resultados se
                  obtendrán cuando el array es de un tipo.
                  Ejemplo:
                     {x} array(SORT)
 
SCAN              Buscará por un elemento, en el array indicado, desde
                  el principio hasta el final.
                  Ejemplo:
                     {5,x}, array(SCAN)
                      
                  buscará el número "5" en el array "x", y si existe,
                  dejará la posición de la primera ocurrecia en el stack.
                  Ejemplo 2:
                     {10,"mensaje",x},array(SCAN)
                  
                  buscará la cadena "mensaje" en el array "x", desde la
                  posición 10 en adelante. Sirve para seguir buscando
                  más ocurrencias, luego de encontrar uno.
                  Ejemplo 3:
                     {100,10,"mensaje",x},array(SCAN)
                  
                  Idem al ejemplo 2, pero solo buscará dentro de las
                  siguientes 100 posiciones del array.
 
SCAN2D            Buscará un elemento, en el array indicado, desde el
                  principio hasta el final.
                  Ejemplo:
                     {2,203,x}array(SCAN2D),mov(nRow)
                  
                  buscará en el array "x", columna "2", el valor "203",
                  y dejará en el stack la posición fila encontrada, o
                  bien, "-1" si no fue encontrado.
  
REVSCAN           Buscará por un elemento, en el array indicado, desde
                  el final hasta el principio del array.
                  Ejemplo:
                     {5,x}, array(REVSCAN)
                  
                  Nota: Todas las opciones de SCAN, se pueden usar con
                  REVSCAN.
  
INSERT            Insertará un elemento, o un array, en la posición
                  indicada dentro del array objetivo.
                  Ejemplo:
                     {1000,10,x}, array(INSERT)
                     
                  Insertará el número "1000" en la posición 10 del
                  array "x".
                  
                  Ejemplo de inserción de un array:
                     {vCosas,10,x}, array(INSERT)
                  
                  El array "vCosas" debe ser 1D, y puede ser MULTITIPO.
                  Dicho array se insertará en la posición 10 de "x", 
                  pero no insertará un array dentro de otro, sino que
                  se añadirán tantas posiciones en "x", comenzando desde
                  la posición 10, como elementos tenga "vCosas".
 
DELETE            Borra una posición desde el array objetivo, compactando
                  dicho array.
                  Ejemplo: 
                     {2,x} array(DELETE)
                  
                  Borrará el elemento de la posición "2" del array "x".
 
ZAPRANGE          Borra un grupo de elementos desde el array objetivo.
                  Ejemplo:
                     {5,10,x} array(ZAPRANGE)
                  
                  Borrará las posiciones desde la "5" hasta la "10", del
                  array "x".
 
CONCAT            Concatena dos arrays.
                  Ejemplo:
                     {g,x} array(CONCAT)
                  
                  Concatena el array "g" al array "x".

RESIZE            Cambia el tamaño del array indicado. Si el nuevo tamaño
                  es menor al tamaño original, los elementos restantes
                  se pierden. Por otro lado, si el nuevo tamaño es mayor
                  que el tamaño original, se añaden posiciones nulas.
                  Ejemplo:
                     {20,x} array(RESIZE)
                     
                  Si el tamaño original de "x" es 10, se añaden 10 nuevas
                  posiciones al final de dicho array.

MENSAJERIA SYSTEM V

HOPPER maneja un set básico de mensajería basada en System V de Linux.

HOPPER               DESCRIPCION                           ALTO NIVEL              

"qcreate"=>306, En progreso. AUN EN ETAPA DE PRUEBAS. "qset"=>307, En progreso. "qsend"=>308, En progreso. "qrecv"=>309, En progreso. "qremove"=>310, En progreso.


TECLADO Y CAPTURA DE TECLAS

HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
kbfree                Elimina el buffer con la última       iskbfree
                      tecla presionada.
 
{C}keyput             Pone la tecla C (representada         keyput(C)
                      como un código ASCII) en el buffer
                      del teclado.
                      Dicha tecla podrá ser consumida
                      por KBHIT?, KBESC?, KBCTRLC? y
                      LASTKEY().
                      Ejemplo:
                      
                      {65}keyput, tecla presionada=0
                      lastkey(tecla presionada)
                      {tecla presionada}print
                      ==> A                      
 
lastkey(T)            Guarda la última tecla presionada     no existe
                      en la variable T. Dicha tecla
                      puede ser obtenida, incluso, con
                      PAUSE.

kbhit?                Devuelve TRUE si se presionó una      iskbhit
                      tecla.

kbesc?                Devuelve TRUE si se presionó la       iskbesc
                      tecla ESC.

kbctrlc?              Devuelve TRUE si se presionó la       iskbctrlc
                      combinación CTRL-C.

INSTRUCCIONES MISCELANEAS

HOPPER               DESCRIPCION                           ALTO NIVEL              
-----------------------------------------------------------------------------------
totalarg              Devuelve el número total de argumentos
                      pasados al programa, desde la línea de
                      comandos. 
                      Si no se pasa ninguno, totalarg=1, que
                      corresponde al nombre del programa.
                      Véase las macros del archivo stdio.hh
                      para más información.

++                    incremento de registro-variable.
                      Si es numérica, aumenta 1 a la variable.
                      Observación:
                      ++v no es lo mismo que v++:
                      ++v incrementa "v" en 1.
                      v++ mete "v" en el stack, y luego, lo
                          incrementa.
                         
                      Nota: no puede ser usada dentro del
                      tag #high-level.
                      
                      Sobrecarga:
                      ++v y v++ eliminan el primer caracter de
                      v, si este es una cadena.
                      Ver OPERADORES SUBORDINADOS para más
                      información sobre "+=", que permite
                      eliminar más de un carcater.
 
--                    decremento de registro-variable.
                      Si es numérica, disminuye 1 a la variable.
                      Observación:
                      --v no es lo mismo que v--:
                      --v decrementa "v" en 1.
                      v-- mete "v" en el stack, y luego, lo
                          decrementa.
                         
                      Nota: no puede ser usada dentro del
                      tag #high-level.
                     
                      Sobrecarga:
                      --v y v-- eliminan el último caracter de
                      v, si este es una cadena.
                      Ver OPERADORES SUBORDINADOS para más
                      información sobre "-=", que permite
                      elimiar más de un caracter.
 
postfix               Fuerza el cálculo en modo NOTACION POLACA.
                      Esta cláusula se usa antes y después de
                      una operación aritmética, o concatenación
                      de cadenas con CAT.
                      De manera natural, las instrucciones de
                      HOPPER sacan los operandos desde el tope
                      del stack, hacia atrás, contradiciendo
                      el modo de operar de la notación polaca.
                      Ejemplo:
                      {2,5}sub   ==> 5-2 = 3   normal
                      postfix,{2,5}sub,postfix
                                 ==> 2-5 = -3
                      Ver CALCULO POSFIJO para más información.
                     
                      Nota: Por defecto, POSTFIX se incluye
                      dentro de las operaciones de #HIGH-LEVEL.
 
back                  Esta instrucción retorna a la instrucción
                      siguiente de una llamada JSUB o GOSUB.
                      No se usa con JMP ni derivados: si lo
                      intenta, obtendrá un error.
                      Normalmente se usa dentro de los bloques
                      de código ubicados luego de .LOCALS, aunque
                      puede usarlo en el módulo principal: en
                      tal caso, HOPPER le dará un WARNING por
                      usar BACK sin haber detectado .LOCALS,
                      pero no se preocupe. Para desactivar los
                      WARNINGS, use la opción "-w".
                     
getstrerror           Cuando usa TRY/CATCH (macros para SWTRAP
                      y GETTRY), se captura el código numérico
                      del error. GETSTRERROR obtiene la
                      cadena descriptiva de dicho error.
                      Es útil cuando se usa en combinación con
                      RAISE (macro de THROW).
                      Puede ser usado dentro de #HIGH-LEVEL. 
                      Ver stdio.hh para más información sobre
                      las macros.
 
true                  Como INSTRUCCION, mete un valor TRUE en 
                      el stack. Como función, asigna un valor
                      TRUE al registro-variable entre paréntesis.
                      Ejemplo:
                      true,println   ==> imprime 1
                      true(v)        ==> v contiene 1 booleano. 
                      
                      Ver CONVERSION DE TIPOS para más información
                      acerca de los valores considerados como
                      booleanos por HOPPER.
 
false                 Hace lo mismo que TRUE, pero con valores
                      booleanos falsos.
                      Ver CONVERSION DE TIPOS para más información
                      acerca de los valores considerados como
                      booleanos por HOPPER.
                      
pause                 Realiza una PAUSA en la ejecución. La tecla
                      presionada para liberar la pausa, es
                      almacenada internamente, y se puede obtener
                      con la instrucción LASTKEY(v), donde "v"
                      contendrá el valor numérico de dicha tecla.
 
clear                 Ver LIMPIAR VARIABLES.
 
dowith                Es una instrucción que guarda internamente
                      un valor numérico, o un valor de cadena,
                      para ser evaluado por las instrucciones 
                      JCASE. 
                      Nota: no es una estructura; por lo tanto,
                      no se permite anidaciones.
                      Ver stdio.hh para más información sobre
                      las macros que usan esta instrucción.
                      
                      NOTA: NO USE ESTA FUNCION: SERA REESCRITA
                      EN EL FUTURO.
                     
jcase                 evalúa el contenido del stack contra el
                      valor almacenado por DOWITH. Si coinciden,
                      pondrá TRUE en el stack, el que puede ser
                      evaluado por DO{}.
                      Ver stdio.hh para más información sobre
                      las macros que usan esta instrucción.
                      
                      NOTA: NO USE ESTA FUNCION: SERA REESCRITA
                      EN EL FUTURO.

{A,B}typechar?       Devuelve TRUE si A es del tipo B      typechar(A,B)
                     B es una cadena.
                     B puede ser cualquiera de los
                     siguientes tipos:
                     tipo    evalúa si A es...
                     ----------------------------------
                     "alnum" (A - Z o a - z) o (0 - 9)
                     "alpha" (A - Z o a - z)
                     "ascii" 0 - 127 (0x00-0x7F)
                     "cntrl" (0x7F o 0x00-0x1F)
                     "digit" (0 - 9)
                     "graph" Imprimibles menos ' '
                     "lower" (a - z)
                     "print" Imprimibles incluido ' '
                     "punct" Signos de puntuación
                     "space" espacio, tab, retorno de línea,
                             cambio de línea, tab vertical,
                             salto de página (0x09 a 0x0D, 0x20).
                     "upper" (A-Z)
                     "xdigit" (0 to 9, A to F, a to f)
 
{A}type              Obtiene el tipo de A.                 type(A)
                     Los tipos de arrays, cuando puede
                     ser evaluado con precisión, están
                     puestos entre "<>".
                     {120},type   ==> "number"
                     {"120"},type ==> "string"
                     true, type   ==> "boolean"
                     Si A es un array de números:
                     {A},type     ==> "<number>"

OBSERVACION. En un array, TYPE comprueba el primer elemento de la matriz. En una matriz multitipo, TYPE puede inducir a un error de percepción ideológicamente falso.

PREPROCESAMIENTO Y METALENGUAJE

Todos los lenguajes de programación son, en sí, metalenguaje de su propia gramática profunda, el ensamblador. HOPPER es un pseudoensamblador, que se programa con sus propias instrucciones, y con metalenguajes definidos por el usuario.

Diseñar los metalenguajes es tarea del programador, y darles vida, es tarea del preprocesador.

El preprocesador de HOPPER es una potentísima herramienta capaz de preprocesar instrucciones tanto formales como del lenguaje pseudo natural: con él, es posible definir instrucciones simulando, por ejemplo, desplazamiento lingüístico. El programador podrá definir metalenguajes en su propio idioma. Además, el preprocesador puede realizar conversión infija a postfija, con lo que, de manera natural, se puede insertar código en lenguaje formal usando instrucciones del TIPO-1, definidas en los tópicos anteriores.

Para realizar el trabajo, HOPPER posee un set de macro-instrucciones potentes que permiten decidir sobre los argumentos a convertir.

Existen definiciones preprogramadas distribuidas en los siguientes archivos:

stdio.hh, string.hh, math.hh, term.hh, time.hh, array.hh, file.hh, hispania.hh

No obstante, se pueden definir cuántos archivo "HH" se necesiten. "HH" significa, Hopper Header.

ULTIMA HORA: TODAS LAS MACROS HAN SIDO FUSIONADAS EN UN SOLO ARCHIVO, LLAMADO HOPPER.H.

RECOMENDACIONES PARA DECLARAR MACROS

1) Si va a declarar una lista de macros con palabras que se repiten, por ejemplo:

  break, break infinity, breaking, etc.
  

declarar las macros con nombres complejos al principio, y dejar la palabra átomo al final. Por ejemplo, la lista anterior debería ser declarada en este orden:

  break infinity, breaking, break.

RAZON: el reemplazo de macros se realiza por ocurrencia de la macro dentro de la línea analizada, no por inspección de la línea, caracter a caracter. De este modo, si reemplaza primero "BREAK", fallará cuando intente encontrar "BREAKINFINITY".

2) Si va a declarar macros con más de un argumento, y éstos son cadenas, se sugiere declararlas usando el tag #DEFN, y asignar cada argumento a una variable de tipo #RNDV-var, y usar esta variable en el cuerpo de la macro.

3) Trate de no declarar macros con nombres que pertenecen a los nombres de las funciones de Hopper.

4) Ponga atención al punto 1.

5) Puede usar una macro #PROTO, una declaración de pseudofunción, dentro de los tags #HIGH-LEVEL, pero no puede usar ningún otro tipo de macros.

DIRECTIVAS PRINCIPALES

Todas las directivas descritas a continuación, deben escribirse con minúsculas, y al principio de la línea, y no deben escribirse entre el código HOPPER, salvo las directivas #DEFINE y #DEFN, que pueden ser incluidas después de MAIN, y las directivas #HIGH-LEVEL, que deben incluirse dentro de MAIN.

Dentro de las directivas del preprocesador, podemos encontrar:

- #!/usr/bin/hopper
- #!/usr/bin/bhopper
- #include
- #define
- #defn
- #prototype, #proto
- #context
- #context-free
- #synonimous, #synon
- #import
- #high-level, #compute, #hl, #fx

DIRECTIVAS INLINE DEFN

La directivas inline son usadas exclusivamente dentro de la directiva #DEFN. Con ellas, se pueden tomar decisiones para expandir la macro en cuestión.

Estas directivas deben escribirse en mayúsculas.

Las directivas inline son las siguientes:

- \
- *
- ;
- #VOID
- #RAND
- #RNDV
- #ATOM
- #ATOMF
- #CMPLX
- #IF / #ELSE / #EIF


DIRECTIVAS DE ESTRUCTURAS

Dentro de las directivas de HOPPER, se encuentran aquellas que permiten simular estructuras de control dentro del código HOPPER, sin requerir #HIGH-LEVEL. Se pueden definir muchas clases de estructuras; para una revisión de las definidas hasta ahora, ver el archivo STDIO.HH.

Las directivas que permiten simular estructuras de control, son las siguientes:

- #LOOP / #ENDLOOP
- ##ENDLOOP
- #CATCH / #ENDCATCH
- ##CODEIF
- #ENDIF
- #ENDIIF
- %LOOP
- %ENDLOOP
- %%LOOP
- %%ENDLOOP
- %CODEIF
- %ENDIF
- %ENDIIF
- %%CODEIF
- &()
- %&
- %%&

La siguiente es una descripción de las directivas inline usadas por el preprocesador de HOPPER. Se pueden encontrar en todos los archivos de cabecera ".HH" o ".H", y sus efectos se pueden hallar compilando un programa con la opción "-p", y consultando el archivo resultante ".PPO".

DETALLE DE DIRECTIVAS DE PREPROCESAMIENTO

DIRECTIVAS PRINCIPALES

#!/USR/BIN/HOPPER Y #!/USR/BIN/BHOPPER

Se puede añadir, en la primera línea de un programa HOPPER, un "bang line", para que el programa sea ejecutado sin escribir "hopper". Por ejemplo, sea el siguiente programa, llamado "holamundo.com":

#!/usr/bin/hopper

main:
  {"hola mundo!\n"} return

Para que el "bang line" funcione, debe cambiar los permisos del programa, como sigue:

 chmod a+x holamundo.com
 

Luego, ejecute con normalidad:

 $ ./holamundo.com

Lo único que hace "bang line", es omitir "hopper"; pero, debe colocar las opciones de todas maneras.

BANG LINE EN UN PROGRAMA BINARIO.

Cuando compila un programa para generar una versión binaria:

  hopper holanumdo.com -x

ésta ya tiene incorporado el "bang line" correspondiente, y es creada con los permisos señalados. En este caso, el "bang line" será:

  #!/usr/bin/bhopper

pues, "bhopper" es el programa que ejecuta las versiones binarias.

De este modo, cuando termine de compilar, solo debe ejecutar el programa:

 ./holamundo

#INCLUDE

Esta directiva permite incluir código de programación y definiciones en un programa HOPPER. Todas las definiciones que se incluyen con esta directiva, son MACROS, y otras directivas.

Cuando se incluyen archivos de definiciones principales, que están guardados en /USR/INCLUDE/HOPPER, se debe usar "<>". Por ejemplo:

 #include <stdio.hh>
 #include <array.hh>
 #include <time.hh>
 #include <math.hh>

etcétera.

Si se desea incluir un archivo definido por el usuario, que no está en el directorio mencionado, no debe usar "<>". Por ejemplo:

 #include user_includes/definiciones.hh
 #include ../include_sistema/def.hh

etcétera.

OBSERVACION. Si el programador desea incluir su archivo de cabecera con "<>", deberá copiarlo al directorio /USR/INCLUDE/HOPPER.


#DEFINE

Hay dos tipos de macros que se pueden definir con #DEFINE: las semejantes a funciones, y las semejantes a objetos. Dentro de los objetos, se pueden contar constantes que refieren a un valor, a expresiones del lenguaje, o a otros objetos.

Ejemplo de macro-objetos:

 #define PI         3.1415
 #define println    {"\n"}print

Ejemplo de macro-función:

 #define main(_X_,_Y_)  main:,_Y_=0,totalarg,\
                        mov(_Y_),getargarray(_Y_,_X_)

OBSERVACION: Dentro de #DEFINE no se pueden definir DIRECTIVAS INLINE: para eso, se debe usar #DEFN.


#DEFN

Esta directiva realiza la misma función que #DEFINE, con la diferencia que con ella es posible usar otras directivas del preprocesador. La diferencia radica en que, mientras con #DEFINE se repasa la misma línea por más ocurrencias de macros, #DEFN solo se concentra en la expansión de las DIRECTIVAS INLINE. Con #DEFN se pueden definir macros-objeto, pero no es necesario.

Ejemplo:

 #defn getargarray(_N_,_V_)  #RAND, V#RNDV=1,_V_={#VOID}, \
                             LOOPGETARG_#RNDV:, {[ V#RNDV ]},\
                             push(_V_),++V#RNDV,{_N_,V#RNDV},\
                             jle(LOOPGETARG_#RNDV),clear(V#RNDV)
 #define main(_X_,_Y_)  main:,_Y_=0,totalarg,\
                        mov(_Y_),getargarray(_Y_,_X_)

El ejemplo muestra una directiva #DEFN necesaria para expandir su invocación en #DEFINE. Las directivas #RAND, #RNDV, #VOID, se describirán más adelante, y son DIRECTIVAS INLINE.


#PROTOTYPE, #PROTO

Esta directiva trabaja solo con macro-funciones; es decir, requiere que las macros definidas tengan argumentos. Su definición es la siguiente:

 #proto NOMBRE_MACRO( <argumentos> )
 

La directiva #PROTO hace referencia a un bloque de código BLOCK:/BACK, usualmente declarado después de la cláusula .LOCALS, por lo que no requiere de código a expandir, como sí lo necesitan #DEFINE y #DEFN. Una macro #PROTO se debe usar, dentro de MAIN:, anteponiendo un guión subrayado "_", como se muestra a continuación:

 _NOMBRE_MACRO( argumentos )

El bloque de código referenciado, se declara como si se tratase de una función:

 #proto NOMBRE_MACRO( argumentos )

Internamente, HOPPER toma la llamada a la macro-función, y la expande como sigue:

 { argumentos },jsub(NOMBRE_MACRO)

y la declaración del bloque, se expande así:

 NOMBRE_MACRO:, varN=0,mov(varN),varN-1=0,mov(varN-1),...,var1=0,mov(var1)

porque, al ser el bloque declarado después de .LOCALS, todas las variables declaradas dentro de un bloque, son locales, y solo existen dentro de ese bloque, hasta donde se encuentre BACK. Esto es muy útil, cuando se realiza un salto hacia otro bloque, pues, se guardan todas las variables locales en un stack de variables locales, las cuales son recuperadas con el retorno.

Ejemplo:

 #proto hanoi(_X_,_Y_,_Z_,_W_)
 main:
    ...
    _hanoi( discos, "A", "B", "C" )
    ...
    {0}return
 .locals
 hanoi(discos,inicio,aux,fin)
    ...
    _hanoi({discos}minus'1',inicio,fin,aux)
    ...
 back

La expansión del código del ejemplo anterior queda como sigue:

 main:
    ...
    {discos,"A","B","C"},jsub(hanoi)
    ...
 .locals
 hanoi:,fin=0,mov(fin),aux=0,mov(aux),inicio=0,mov(inicio),discos=0,mov(discos)
    ...
    {discos}minus(1),{aux,inicio,fin},jsub(hanoi)
    ...
 back

Con la directiva #PROTO, se pueden simular funciones, y estas pueden ser RECURSIVAS. Esto se logra gracias a que, luego de detectarse una "pseudofunción" luego de la cláusula .LOCALS, cualquier llamada con JSUB, de manera interna, es "rodeada" con instrucciones IPUSH, antes, e IPOP después de JSUB, lo que almacenará las variables declaradas dentro del bloque de la pseudofunción en un stack interno de datos.

#CONTEXT

Esta directiva es semejante a #PROTO, en cuanto a que solo requiere la declaración del nombre de la macro, pero no usa argumentos; es útil para declarar CONTEXTOS de programación, grupos de bloques que realizan una tarea determinada, bajo un nombre conceptual.

 #CONTEXT  NOMBRE-MACRO

NOMBRE-MACRO se expande a GOSUB, es decir, realiza un salto con retorno al bloque indicado, siempre y cuando, en el stack exista un valor de verdad TRUE o asociado (no nulo, distinto de cero, cadena no vacía).

El nombre de la macro bajo #CONTEXT se invoca sin anteponer "_".

Ejemplo:

 #include <hopper.h>
 
 #context dividir por la raíz de 2
 #define  imprimeconunsalto  {"\n"}print
 #define  luego    emptystack?\
                   do{{"No puedo continuar por falta de datos "},\
                   throw(1000)}
 main:
    {10,5}mul; dividir por la raíz de 2
    luego, imprime con un salto
 {0}return
 .locals
 dividir por la raíz de 2:
    div by ({2},sqrt)
 back

El ejemplo anterior se expande a lo siguiente:

 main:
 {10,5}mul; gosub(dividirporlaraízde2)
 emptystack?do{{"No puedo continuar por falta de datos "},throw(1000)},{"\n"}print
 {0}return
 .locals
 dividirporlaraízde2:
 {2},sqrt;postfix;div;postfix
 back

Primero, multiplica 10 y 5; luego, se activa GOSUB (la declaración de #CONTEXT), porque en el stack hay un dato distinto de cero o vacío, y va al bloque en cuestión para realizar el cálculo. Al retornar, imprime el resultado.

Nota: si el resultado de la operación hubiese sido "0", GOSUB no se activa, e imprime "0".

#CONTEXT-FREE

Esta directiva es semejante a #CONTEXT, pero, como su nombre lo indica, es libre del contexto, es decir, no requiere de ningún dato distinto de cero y de vacío en el stack para funcionar, porque no expande a GOSUB, sino, a JSUB (ver ambas instrucciones, para más información).

Ejemplo:

 #include <stdio.hh>
 #include <math.hh>
 
 #context-free obtenercuadradodepi
 #define  imprimeconunsalto  {"\n"}print
 #define  luegode  emptystack?,not,\
                   do{{"No puedo continuar por datos en el stack "},\
                   throw(1000)}
 main:
    luego de obtener cuadrado de pi, imprime con un salto
 {0}return
 .locals
 obtener cuadrado de pi:
    {M_PI} pow by '2'
 back

El código anterior expande a:

 main:
 emptystack?,not,do{{"No puedo continuar por datos en el stack "},throw(1000)}jsub(obtenercuadradodepi),{"\n"}print
 {0}return
 .locals
 obtenercuadradodepi:
 {3.14159265358979323846}powby'2'
 back

#SYNONYMOUS, #SYNON

Esta directiva permite asociar frases o palabras sinónimas de cualquier cosa definida con las directivas anteriores.

Ejemplo:

 #context-free obtenercuadradodepi
 #define  imprimeconunsalto  {"\n"}print
 #define  luegode  emptystack?,not,\
                   do{{"No puedo continuar por datos en el stack "},\
                   throw(1000)}
 #define  luego    emptystack?,\
                   do{{"No puedo continuar por falta de datos "},\
                   throw(1001)}
 
 #synon obtenercuadradodepi  calcularelcuadradodepi, obtenerpialcuadrado
 #synon luegode              finalmente, aparte, porotrolado
 
 main:
    luego de calcular el cuadrado de pi, imprime con un salto
    finalmente, obtener pi al cuadrado; luego, imprime con un salto
 {0}return
 .locals
 obtener cuadrado de pi:
    {M_PI} pow by '2'
 back

Lo anterior expande a:

 main:
 emptystack?,not,do{{"No puedo continuar por datos en el stack "},throw(1000)}jsub(obtenercuadradodepi),{"\n"}print
 emptystack?,not,do{{"No puedo continuar por datos en el stack "},throw(1000)},jsub(obtenercuadradodepi);emptystack?,do{{"No 
 puedo continuar por falta de datos "},throw(1001)},{"\n"}print
 {0}return
 .locals
 obtenercuadradodepi:
 {3.14159265358979323846}powby'2'
 back

CONSIDERACIONES CON #PROTO.

Para declarar sinónimos con macros definidas por #PROTO, hay que hacerlo anteponiendo "_" al nombre de la macro original, y al sinónimo. Ejemplo:

 #proto cálculo(_X_,_Y_)
 
 #synon _cálculo   _operación, _obtencióndelresultado

Y la llamada, será como se hace con la macro original:

 _operación (2,100.6)
 _obtención del resultado ({M_PI}mulby(180), 0.25)

#IMPORT

Esta directiva importa todos los códigos disponibles en el archivo-librería declarado bajo esta cláusula, y los añade al final del programa, en tiempo de compilación. Una librería se crea con la opción "-l":

  hopper script.com -l -o <directorio-libreria>

El archivo generado tendrá el nombre del archivo original (incluyendo su extensión), más la extensión ".lib", y será una versión preprocesada del archivo-librería original, donde se excluyen todas las líneas anteriores a .LOCALS, inclusive.

Si va a crear una librería con PROTOTIPOS (#PROTO), es necesario incluir las directivas #PROTO en el programa objetivo, ya sea de manera directa, o a través de la inclusión de un archivo de cabecera.

EJEMPLO COMPLETO DE GENERACION Y USO DE UNA LIBRERIA.

El siguiente ejemplo sencillo, generará una librería para imprimir un mensaje, mediante la declaración de un prototipo.

1) El archivo de prototipos (PROTOTIPOS.HH):

 #proto muestraotromensaje(_X_)
 #synon _muestraotromensaje   _mensajefinal

2) El programa librería (UTILIDADES.COM):

 #include prototipos.hh
 
 main:
 {0}return
 
 .locals
 muestra otro mensaje(m)
    {"Otro mensaje:\n---- ",m,"\n"}print
 back

3) Compilar UTILIDADES.COM:

  hopper src/utilidades.com -l -o lib
  Si todo ha ido bien, mostrará el siguiente mensaje:
  Generating library "lib/utilidades.com.lib"...
  
  El archivo creado será el indicado entre apóstrofes, en la ruta señalada.

4) El programa principal (MENSAJE.COM):

 #include <stdio.hh>
 #include prototipos.hh          // incluye el header de prototipos
 #import lib/utilidades.com.lib  // importa la librería
 main:
   _muestra otro mensaje ({30}mulby(3.1415);xtostr)
   _mensaje final ({"\G"}{"Mensaje final!!\n"};upper;pol(cat);{"\OFF"};pol(cat))
 {0}return
 .locals
 
 Importante: DEBE AÑADIR .locals al final del programa, si no escribe bloques.
 

5) Ejecución normal:

 Ejecutar con "hopper src/mensaje.com"
 
 El programa devolverá:
 
 Otro mensaje:
 ---- 94.245
 Otro mensaje:
 ---- MENSAJE FINAL!!

#HL, #HIGH-LEVEL, #FX, #COMPUTE

HOPPER es una máquina virtual que posee su propio ensamblador: es todo el lenguaje que se ha revisado en esta ayuda. Este código, hechas las expansiones de macros, se traduce directamente a HOP-CODE, que es el código binario ejecutable. Es decir, cuando se escribe algo como esto:

  {100} mul by '{20.0} minus (M_PI)',{"\n"} print

se obtiene una traducción literal en HOP-CODE, lo que reduce el tiempo de compilación y de ejecución. Nótese que los cálculos se realizan tal como los realiza una persona en su mente, siguiendo los procedimientos "naturales": uno jamás calcula, con la mente, de la manera "infija", como una expresión matemática, atiborrada con paréntesis.

Por ejemplo: enunciar el Teorema de Pitágoras, no es lo mismo que calcularlo. Cuando lo calculamos, seguimos un procedimiento semejante a éste:

 "obtén el cuadrado del cateto mayor, y el cuadrado del cateto menor; luego, súmalos, y calcula su raíz cuadrada"

y eso es lo que hace HOPPER:

 "{cateto mayor}pow by '2',{cateto menor}pow by'2',add,sqrt"

Esto es una ventaja, hasta cierto punto; es, también, la idea detrás del Proyecto Hopper: programar, en primera instancia, siguiendo una lógica más cercana a la "natural", pensando en una, no muy lejana, "programación hablada".

No obstante, esto puede ser un poco engorroso, a la hora de programar expresiones como la fórmula del Teorema de Bayes, o cualquier otra fórmula que se "ve" má simple programada de la manera tradicional, es decir, "infija".

Para cosas como esta, se pensó la directiva #HIGH-LEVEL (junto con sus sinónimos).

La directiva #HIGH-LEVEL permite codificar en notación infija, una línea de programa, o un bloque.

1) Línea de programa:

  #hl ( c = sqrt( (cateto mayor^2) + (cateto menor^2) ) )
  #hl ( (i+30)*M_PI/180 ), mov(resultado)

  o su equivalente:

  #hl ( resultado = (i+30)*M_PI/180 )

2) Bloque de programa:

  #hl{
     x += 10*tasa^2+0.5
     print("Resultado: ",x,"\n")
     if (x>5)
        print("Tiene haber!\n")
     else
        print("Tiene debe!\n")
     end if
  }

Como se puede ver en los ejemplos, cuando se programa #HIGH-LEVEL en bloque, además de facilitar la escritura de expresiones complejas, se pueden usar algunas estructuras.

IMPORTANTE: SOLO SE PERMITE ESCRIBIR UNA INSTRUCCION POR LINEA.

IMPORTANTE: NO PUEDE ESCRIBIR MACROS #HIGH-LEVEL DENTRO DE OTRAS MACROS.

Asimismo, en el ejemplo 1 queda de manifiesto que el resultado de la expresión se guarda en el stack, que luego será usado por la instrucción MOV. Esto revela la directa imbricación entre código de alto nivel y código HOPPER.

IMPORTANTE: TODAS LAS INSTRUCCIONES QUE OBTENGAN SUS ARGUMENTOS DESDE EL STACK, PUEDEN SER USADAS EN #HIGH-LEVEL. AQUELLAS INSTRUCCIONES QUE REQUIERAN PARENTESIS, COMO POR EJEMPLO, JMP(), NO PUEDEN SER USADAS AQUI.

En cuanto a las instrucciones que terminan con un signo de interrogación, como por ejemplo, "NUMERIC?", existe una versión "infija", como en este caso: "ISNUMERIC". Cada una de las versiones, han sido añadidas en el transcurso de la descripción de dichas instrucciones.


ARREGLOS

Los arreglos se tratan aquí como en cualquier lenguaje, salvo por una restricción: se pueden hacer cálculos dentro de los corchetes [], pero no se pueden usar funciones, ni tampoco indización (arrays dentro del índice del array).

 p[(1+1):3], X[n,(n-1)], etc.
 

En general, los arrays se acceden como en HOPPER. Ejemplo:

 ++i, --j
 #hl( x[i:end,j] = i*j + a[i:end,j] )

Como ejemplo de lo útil que es #HIGH-LEVEL, a pesar de las restricciones, aquí va la expansión del código anterior:

 ++i,--j
 postfix,
 {i}{j}mul,[i:end,j]get(a),add,[i:end,j]put(x)
 postfix,

Si el código anterior se escribiese directamente en HOPPER, quedaría algo así:

 ++i,--j,{i}mul by'j',plus '[i:end,j]get(a)', put(x)

¿Por qué se escribe solo una vez "[i:end,j]"? Porque basta con una declaración, para que quede disponible al resto del programa. De ahí que es necesario CLEARMARK.

IMPORTANTE. Todos los arreglos se declaran en HOPPER, no en #HIGH-LEVEL.


ASIGNACION MULTIPLE

Se aceptan asignaciones múltiples:

 a = b = c = 0
 
 x = y = log(x+y)^(log(x-y) )
 
 p[2]=p[5]=p[7,1]=x=1000
 
 y = p[1] = p[10:14,2:4] = (-1)
 
 p[j]=5


ASIGNACION INTERMEDIA

Se aceptan asignaciones a variables, dentro de una expresión. Por ejemplo, en la siguiente expresión:

 #hl{
    r=(x:=(2+1))*2^(x-1)
 }

Se calcula "2+1", se deja el resultado en "x", pero también se deja disponible para el resto del cálculo.

Internamente, ":=" hace referencia al uso de la instrucción CPY, que copia un dato desde el top del stack, sin extraerlo, como sí lo hace MOV.

La expansión del código anterior, es la siguiente:

 postfix,clrmarksall
 {2}{1},add,x=0,cpy(x),{2}{x}{1},sub,pow,mul,r=0,mov(r)
 postfix,clrmarksall

OBSERVACION. Si "r" es una matriz, el resultado se copiaría a todas sus posiciones. Recordar que MOV no considera marcas de intervalos.

OBSERVACION 2. Las marcas y los intervalos son eliminados antes de entrar a ejecutar una macro #HL.

ESTRUCTURAS DE CONTROL

Se admiten las siguientes estructuras de control:

- WHILE/WEND
- IF/ELSEIF/ELSE/ENDIF
- DO/UNTIL
- <expresion> ? <si es true> : <si es false> ;

La última estructura, llamada BIFURCACION INLINE, debe terminar con semicolon, ";".

Para mayor información sobre operadores lógicos y aritméticos de alto nivel, ver CODIGO DE ALTO NIVEL.


ANDAMIENTO DE INSTRUCCIONES

Todas las estructuras de control en #HIGH-LEVEL permiten anidamiento.

EJEMPLOS:

WHILE:

 i=0
 #hl {
    while (i <= 10 )
       print("I = ",i," => ",(i*2),"\n")
       i=i+1
    wend
 }
 

IF:

 #hl{
    if ( (i==0) && (log(j*10)>2) )
       x /= 2
    end if
 }

DO:

 i=10
 #hl{
    do
       print(i,"\n")
       i=i-1
    until (i==0)
 }
BIFURCACION INLINE
v=2
x = x + (v==1) ? (100+(v-5)) : (v==2 && true) ? (200*4-1) : (-1); + 0.5 
y = cat ( cat( "hola", (v==2)?" Mundo":" amigo"; ), (v==3)?(w:=" infame!"):" genial!";)


EXPANSIONES PROBLEMATICAS

Algunas expansiones desde #HIGH_LEVEL no son tan óptimas como sus homólogas HOPPER. Para más información, ver CODIGO DE ALTO NIVEL. En tal caso, se recomienda intercalar código HOPPER entre bloques de #HIGH-LEVEL. Por ejemplo:

 i=0
 #hl {
      while (i <= 10 )
         print("I = ",i," => ",(i*2),"\n")
 }
         ++i
 #hl( wend )

Donde "++i" en HOPPER es mucho más óptima que "i=i+1" en #HIGH-LEVEL. ASimismo, "i=0" en #HIGH-LEVEL es "{0},mov(i)", en cambio, en HOPPER es eso, "i=0".

Donde sí es muy práctico usar #HIGH-LEVEL, es en las expresiones lógicas, debido a que expanden a código HOPPER sin pérdida de optimización.

DIRECTIVAS INLINE PARA #DEFN

DIRECTIVA "\"

No es una directiva propiamente tal, dado que puede ser usada a lo largo de todo el programa; no obstante, se incluye aquí por razones de apoyo a la definición de directivas.

Su función es separar líneas de programación demasiado largas. En la definición de macros, esto es muy necesario, más que en la programación misma.

Ejemplo:

 #defn uniformrand(_MU_,_FVAR_,*)  #RAND,_V_#RNDV=0,{*}randarray(_V_#RNDV),\
                                   {_V_#RNDV}mulby(2),minus(1),mulby(_FVAR_),\
                                   plus(1),mulby(_MU_),clear(_V_#RNDV)

Dentro del código principal de programación, se puede esar para aquellas macros que se anidan, por ejemplo:

 #include <hopper.h>
 main:
    true(i)   
    iif(false,{"Es verdad\n"},\
        iif(true,{"Es verdad por 2 intento\n"},{"Es falso"})), println
    exit(0)

En el ejemplo, la macro IIF, ubicada dentro de STDIO.HH, anidada, extiende la programación; en este caso, es muy útil "\".


DIRECTIVA "*"

Es un comodín, que será reemplazado por cualquier cosa que encuentre en el argumento. Este comodín es FINAL, es decir, debe colocarse como último argumento de la macro. HOPPER define un argumento hasta que encuentre una coma ",", o un paréntesis cerrado ")". Luego, "*" se saltará las ",", y dejará todo lo que encuentre hasta ")", como un único argumento. Ejemplo:

 #define forall(_X_,_R_,*)    _R_,get(_X_),*,put(_X_)

Si la macro es invocada como sigue:

 for all (arr,[i:end,j],mulby'10',minus'j',ceil)

expandirá:

 [i:end,j],get(arr),mulby'10',minus'j',ceil,put(arr)
 ----v----     -v-  ----------v------------     -v-
    _R_        _X_            *                 _X_


DIRECTIVA ";"

El semicolon es útil para forzar que el preprocesador de macros de HOPPER lea una expresión, como un único argumento. Dicho caracter puede ser usado en el código de programa, en la invocación de macros. Este caracter es usado por HOPPER de la misma forma en que usa ",", como separador de instrucciones; sin embargo, en tiempo de preprocesamiento, ";" no es considerado como separador de instrucciones.


#VOID

Se usa para colocar un VACIO, o sea, será reemplazado por NADA. ¿Para qué sirve? En primera instancia, no puede declarar, en una macro, un PUSH vacío, o sea, "{}", porque HOPPER lanzará error. Si necesita declarar, dentro de su macro, algo como esto:

  _X_={}
  

que no declara un PUSH VACIO, sino, que inicializa una variable como un array vacío, debe hacerlo así:

  _X_={#VOID}


#RAND

Genera un número pseudo-aleatorio, con la siguiente fórmula:

  ceil(rand()*1000000)

donde se devolverá un número entre 10000 y un millón.

Este número reemplazará todas las instancias de #RNDV dentro de la actual expansión. Se coloca al principio del desarrollo de la macro, y desaparece en la expansión. Ejemplo:

 #defn uniformrand(_MU_,_FVAR_,*)  #RAND,_V_#RNDV=0,{*}randarray(_V_#RNDV),\
                                   {_V_#RNDV}mulby(2),minus(1),mulby(_FVAR_),\
                                   plus(1),mulby(_MU_),clear(_V_#RNDV)

En el ejemplo, se crea un número pseudo-aleatorio, y éste reemplaza a todas las instancias #RNDV, creándose variables intermedias. Por ejemplo, si #RAND libera 28973, todas las instancias _V_#RNDV se convierten en _V_28973.


#RNDV

Se usa para declarar variables intermedias dentro de una macro, o bien, para establecer valores aleatorios para otros fines. El número pseudo-aleatorio es creado por #RAND, y será el mismo para todas las instancias de #RNDV existentes en la presente expansión. Ejemplo:

 #defn rnd   #RAND, {#RNDV}divby(1000000)

Esta macro generará un número aleatorio entre 0 y 1, igual que la instrucción RAND:

 {1},rand


#ATOM#CMPLX, #ATOMF#CMPLX

Verifican si el argumento de la macro es un escalar (constante o variable), o una expresión. Normalmente, se usan combinadas. Ejemplo:

 #defn  addto(_X_)  #ATOM #CMPLX;add

La macro ADDTO espera que exista un valor en el stack. Si el argumento _X_ es un número o una variable, reemplazará a #ATOM y lo colocará entre "{}", o sea, declarará un PUSH de ese dato; si es una expresión compleja (una operación), reemplazará #CMPLX, pero omitirá el uso de "{}". Es decir:

 {100},add to (5)           ==> expande: {100}{5};add
 {100},add to ({x}minus(1)) ==> expande: {100}{x}minus(1);add

RESTRICCION. Cada combinación #ATOM#CMPLX reemplaza a un solo argumento de la macro, en la posición señalada. Por ejemplo:

 #defn  between(_X_,_Y_)     #ATOM#CMPLX;#ATOM#CMPLX;between?
                             -----v----- -----v----- 
                                 _X_         _Y_

Es decir, primer argumento obtenido, reemplaza a primera instancia de #ATOM#CMPLX; segundo argumento, reemplaza a la segunda instancia de #ATOM#CMPLX, y así sucesivamente.


#IF/#ELSE/#EIF

Permite al preprocesador de HOPPER la toma de decisiones dentro de una macro particular. Ejemplo:

 /* sobrecarga de instrucciones aritmeticas naturales: MINUS */
 #defn MINUS(_X_)    #IF minus(#ATOMF) #ELSE #CMPLX;postfix;sub;postfix #EIF

MINUS es más rápido que SUB, pero solo admite una constante o una variable. La macro no puede sobrecargar al operador real, por lo que se hace necesario escribirla, en este caso, con mayúscula.

 {5}minus(x)              ==> expande a: {5}minus(x)
 {5}MINUS({x}mul by'10')  ==> expande a: {5}{x}mul by'10';postfix;sub;postfix

Recordar que POSTFIX activa/desactiva el cálculo en notación polaca. Para el ejemplo, calculará 5 - x*10; sin POSTFIX, HOPPER calcularía x*10-5, y no cumpliría con el sentido de la operación MINUS.

#LOOP / #ENDLOOP, %LOOP / %ENDLOOP

Sirven para definir estructuras de CICLO. Con estas directivas, es posible simular estructuras de control WHILE, familia de FOR, REPEAT/UNTIL, y cualquiera que pueda imaginar.

La directiva #LOOP devuelve una etiqueta aleatoria para usarla como declaración de un bloque. Dicha etiqueta es colocada en un stack, el que será consultado por %LOOP para "cerrar" la estructura. Asimismo, #ENDLOOP devuelve otra etiqueta aleatoria, que señala el fin de la estructura, y la coloca en el stack de estructuras. Dicha etiqueta será consultada por %ENDLOOP.

El stack de etiquetas permite la anudación de estructuras.

Ejemplo:

 #defn while(*)          &( ) #LOOP:,*,jnt(#ENDLOOP)
 #defn for(_X_,_Y_,_Z_)  &(_Z_) _X_,  #LOOP:, _Y_, jnt(#ENDLOOP)
 
 #defn wend              %&,  jmp(%LOOP), %ENDLOOP:
 #synon wend             next

Las directivas %LOOP y %ENDLOOP sacan la etiqueta del stack de estructuras.

DIRECTIVAS &(), %&

Son directivas que sirven para declarar argumentos que servirán para iterar una estructura. Por ejemplo, en FOR, "&(_Z_)" guarda en el stack de iteración el argumento "_Z_", y "%&" lo rescata. Ejemplo:

Para la declaración FOR en STDIO.HH:

  #defn for(_X_,_Y_,_Z_)  &(_Z_) _X_,  #LOOP:, _Y_, jnt(#ENDLOOP)

si usamos:

 for( x=1; y=100, {x}lethan'100', ++x;--y)
    //...
 next
 

esto expandirá a esto:

para FOR:

 x=1;y=100,____CODE_JUMP____636154361:,{x}lethan(100),jnt(____CODE_JUMP____687826442)
 ----v---- ------------v-------------  -------v------     ------------v-------------
    _X_              #LOOP                   _Y_                  #ENDLOOP

para NEXT:

 ++x;--y,jmp(____CODE_JUMP____636154361),____CODE_JUMP____687826442:
 ---v---     ------------v-------------  -------------v------------
   _Z_                 %LOOP                      %ENDLOOP


%%&, %%LOOP y %%ENDLOOP

La directiva "%%&" obtiene el argumento de iteración (incremento o decremento, o lo que sea), sin sacarlo del stack de iteración.

Por otro lado, %%LOOP y %%ENDLOOP obtienen las etiquetas del stack de iteración, sin sacarlas del stack.

Lo anterior es útil para definir macros como CONTINUE y BREAK, que continúa la iteración desde un punto interior del bucle, o lo termina antes de que finalice el ciclo normal.

Ejemplo:

 #defn continue             %%&, jmp(%%LOOP)
 #defn break                jmp(%%ENDLOOP)

Si en el ejemplo del ciclo FOR anterior, existiese un CONTINUE, éste expandiría así:

  ++x;--y, jmp(____CODE_JUMP____636154361)

y si existiese un BREAK, expandiría así:

  jmp(____CODE_JUMP____687826442)


##ENDLOOP

Para ciertas estructuras de control, se hace inevitable obtener la etiqueta de fin de estructura, antes de programar el código de expansión de dicha estructura. Esto se puede apreciar en las macros de los ciclos especiales FOR, en STDIO.HH.

Dicha etiqueta es usada dentro del código, varias veces, y esto es por lo que se vuelve necesario tenerla de antemano.

Ejemplo:

 #defn foreachline(_Y_,_X_,_V_)  #RAND, ##ENDLOOP\
                                 &(++_V_;{_V_#RNDV,_V_},jgt(%%ENDLOOP)),\
                                 _Y_="", _V_=1, _V_#RNDV=0, {"\n"}toksep,\
                                 totaltoken(_X_), cpy( _V_#RNDV ), \
                                 jz(%%ENDLOOP), #LOOP:,{_V_},$(_X_),mov(_Y_)

Ejemplo:

 #include <stdio.hh>
 main:
   s="Juanito juega a la pelota\nMaría tenía un corderito\nPablito clavó un clavito"
   v="",indice=0
   for each line(v, s, indice)
      prnlutf8(v)
   next
 return(0)
 

Lo que hace esta macro, es iterar para cada línea (terminada con "\n") de la cadena analizada.

IMPORTANTE. La diferencia entre #ENDLOOP y ##ENDLOOP, es que este último no queda registrado en la expansión; en cambio, el primero sí.


#CATCH / #ENDCATCH, %CATCH / %ENDCATCH

Estas directivas se añadieron exclusivamente para simular TRY/CATCH con las instrucciones de HOPPER, que son menos amables.

La lógica de "#" y "%" es la misma vista en las directivas de CICLO.

Ejemplo (stdio.hh):

 #defn try               swtrap( #CATCH )
 #defn raise(_ERR_,_M_)  {_M_}, throw(_ERR_)
 #defn catch(_X_)        jmp(#ENDCATCH), %CATCH:,\
                         clearstack,_X_=0,gettry(_X_)
 #defn finish            %ENDCATCH:, popcatch

Las directivas #CATCH y #ENDCATCH son almacenadas en el stack de estructuras, por lo que también permiten anidamiento.


##CODEIF / #ENDIF, %CODEIF / %ENDIF, %%CODEIF

Estas directivas se diseñaron para simular estructuras de BIFURCACION, aunque no necesariamente se remiten a IF/ELSE. Con ellas, es posible dar vida a la estructura IF/ELSEIF/ELSE/ENDIF, y a otras, como se mostrará a continuación.

Ejemplo (stdio.hh y hopper.h):

 #defn elseif(*)       jmp(%%CODEIF), %ENDIF:, *, jnt(#ENDIF)
 #defn if(*)           ##CODEIF, *, jnt(#ENDIF)
 #defn else            jmp(%%CODEIF), %ENDIF:, true,jnt(#ENDIF)
 #defn endif           %CODEIF:, %ENDIF:

NOTA: se coloca ELSEIF antes de IF, porque el preprocesador se confunde si están al revés.

La directiva ##CODEIF crea una etiqueta aleatoria, y la guarda en el stack de bifurcación. #ENDIF creará una única etiqueta aleatoria, que será consumida por ELSEIF o por ELSE, asegurando el funcionamiento de la estructura. ELSEIF o ELSE, consumen la etiqueta (%ENDIF), y a continuación, crean una nueva con #ENDIF.

La lógica de "%%" es la misma vista en las directivas anteriores.

Un ejemplo de una bifurcación natural, se muestra a continuación, donde se espera que exista un dato en el stack, y que sea TRUE o similar:

 #defn  segúnloanteriorhazesto    ##CODEIF,jnt(#ENDIF)
 #defn  hastaaquí                 %CODEIF:,%ENDIF: 


#ENDIIF / %ENDIIF

Estas directivas fueron creadas para simular una BIFURCACION INLINE, semejante a la descrita en #HIGH-LEVEL, aunque no la misma.

Ejemplo (hopper.h):

 #defn iif(_X_,_Y_,_Z_)     #ATOMF#CMPLX, jnt(#ENDIIF),\
                            #ATOMF#CMPLX, jmp(#ENDIF),\
                            %ENDIIF:, #ATOMF#CMPLX, %ENDIF: 

La macro IIF requiere de #ENDIF, que la usa para reservar una etiqueta de salto, si la expresión _X_ es TRUE.

IMPORTANTE: esta macro permite anidamiento.

Ejemplo:

 #include <hopper.h>
 main:
    false(i)
    iif(i, {"Es verdad\n"},\
       iif(true,{"Es verdad por 2do intento\n"},{"Es falso"}))
    println
    exit(0)

imprimirá: "Es verdad por 2do intento"


FIN DE AYUDA INTRODUCCION A HOPPER

Pages in category "Amazing Hopper"

The following 124 pages are in this category, out of 124 total.