Category:Amazing Hopper
This programming language may be used to instruct a computer to perform a task.
See Also: |
|
---|
___ _____ ___ __ __ ___ ___ ___ ___ | |\ | | | \ / \ | | / \ | \ | \ | | \ | | \ | | | | | | | | | | | | | | | | | | | \| | |__/ | | |__| | | |__/ |__/ |_ |__/ _|_ | | | | \ \__/ | | \__/ | | |__ | \
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.
- CONTEXT, internamente, usa las instrucciones KEEP,GOSUB.
- 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 127 pages are in this category, out of 127 total.
2
A
C
E
F
G
H
L
M
P
R
S
- Smith numbers
- Snake
- Sort three variables
- Split a character string based on change of character
- Statistics/Basic
- Stern-Brocot sequence
- String append
- String case
- Strip a set of characters from a string
- Strip whitespace from a string/Top and tail
- Substring/Top and tail
- Successive prime differences
- Sum of a series
- Super-d numbers
- Symmetric difference
- Sync subtitles