Esquema del efecto de la instrucción set name,data sobre la memoria de pares.
2- memoria de pares:
La estructura Jenoassembler-hax dispone de dos memorias principales muy diferentes (aunque ya veremos que en realidad son tres), una de ellas es la memoria del programa, donde contendremos el código que se va a ejecutar y que es capaz de localizar nativamente la dirección de etiquetas y de procedimientos. Ésta memoria apenas tenemos que administrarla una vez cargado el programa, tan sólo modificaremos el puntero con las instrucciones de flujo. La otra memoria es llamada memoria de pares, puesto que en realidad son dos memorias emparejadas, así podremos guardar parejas de "nombre"-"valor" que pueden ser accesadas usando su nombre como parámetro. El primer bloque de la memoria de pares, la que contiene los nombres es capáz de buscar automáticamente y devolver el valor contenido en el otro bloque. En síntesis es un sistema que funciona más o menos como las variables de toda la vida. Vamos a efectuar una operación compuesta de ejemplo usando la memoria de pares para los datos temporales: (2+3) + (3-2) o lo que es lo mismo en notación infija postasignación: 2+3[a]3-2[b]a+b[res] // 1- efectuamos la primera operación y guardamos el resultado en la memoria de pares // su nombre será tmp01, usaremos el prefijo de literal puesto que tmp01 es un nombre, no un valor dinámico mode #int set0 #2 set1 #3 suma set #tmp01,core_reg2 // 2- tenemos el valor contenido en tmp01 // seguimos con la siguiente operación y la guardamos como tmp02: set0 #3 set1 #2 resta set #tmp02,core_reg2 // 3- ahora deberemos asignar los dos resultados a los dos registros principales para operar con ellos // los resultados intermedios están contenidos den tmp01 y tmp02: set1 tmp02 set0 tmp01 suma // 4- listo, ahora tenemos el resultado final en core_reg2, podemos escribirlo o usarlo: busout #3,core_reg2 Cada vez que usamos la instrucción set hay que comprobar si el nombre de variable existe o no, y como es obvio, cada lectura de un valor de estos efectua otra búsqueda. No son especialmente lentas, en realidad depende del número de objetos contenidos en la memoria de pares, es decir, su coste es O(n) donde n es el número de nombres en memoria, así que el uso de registros en lugar de memoria de pares resulta más rápido siempre y cuando La máquina virtual haxbox dispone de tres cadenas de registros de uso general, llamados reg0, reg1 y reg2. El hecho de que sean tres y no dos o diez se explica fácilmente, dos de ellos están destinados a contener operandos y el tercero el resultado de cada operación básica, ya sea con números o con cadenas. Así pues si queremos diseñar un código que suma 2 + 3 el procedimiento sería el siguiente:
// 1- declarar el modo de los registros reg0 y reg1 // para operar deben de ser iguales: mode0 int mode1 int // 2- introducir los operandos a los registros respectivos: set0 2 set1 3 // 3- operar suma // 4- ahora tenemos el resultado contenido en reg2 y podemos enviarlo a la consola de salida // core_reg2 es la palabra reservada para recuperar el valor de reg2 busout 3,core_reg2 El código anterior funciona bien, pero no está en absoluto optimizado. Haxbox es muy permisivo en ciertos sentidos, en el punto 2 (set0 2 y set0 3) queremos guardar los operandos en los registros generales, pero el intérprete va a tratarlos como si fueran valores dinámicos, así que comprobará si pueden ser interpretados como variables, direcciones de memoria, fracciones de array o cualquier otro registro de la estructura. Puede que apenas notemos diferencia, pero esto llevará una fracción de tiempo que podemos ahorrar avisando a la máquina de que se trata de valores literales con el prefijo #, lo usaremos también en la instrucción modeX para indicar el modo y busout para indicar el satelite con el cual comunicar, no asi en el caso del segundo parámetro, ya que core_reg2 forma parte de la estructura dinámica: // 1 mode0 #int mode1 #int // 2 set0 #2 set1 #3 // 3 suma // 4 busout #3,core_reg2 Al usar el prefijo literal (#) conseguimos saltarnos una serie de búsquedas de valor dinámico que no darán ningún resultado y por lo tanto devolverá el mismo valor de entrada, por lo que se trata de trabajo inútil y debemos evitarlo. Por otro lado existe una órden para definir los modos de los tres registros en un mismo valor, en lugar de mode0 y mode1 podemos usar mode [tipo] lo cual nos ahorra una línea, así tendremos: mode #int set0 #2 set1 #3 suma busout #3,core_reg2 Ahora si podemos decir que tenemos el código optimizado, la ejecución de este código dará como resultado una línea de texto en la consola en la que vendrá escrito el resultado de 2+3, es decir 5. Cada operación debe de ser efectuada de una en una y, como hemos dicho el resultado lo encontraremos siempre en core_reg2. Hemos dicho antes que haxbox dispone de tres cadenas de registros, en este caso sólo hemos usado el primer registro de cada cadena. Para cambiar entre unos registros y otros de cada cadena hay que modificar el valor del puntero de cada registro. Para ello existe la órden pX, donde x indica un identificador numérico (0, 1 o 2) y a la que le añadimos un parámetro que será la nueva posición del puntero. Imaginemos que queremos realizar una operación similar a la siguiente: (2+3) + (3-2) o lo que es lo mismo en notación infija postasignación: 2+3[a]3-2[b]a+b[res] Como hemos explicado deberemos efectuar las operaciones de una en una y siempre con dos operandos, en este caso debemos efectuar tres operaciones, dos de ellas con valores literales y otra última con los resultados de las mismas. Tres registros se nos quedan cortos si queremos efectuar esta operación sin desperdiciar la memoria por eso haxbox dispone de cadenas de registros. Debemos usar la instrucción p0, p1 y p2 para guardar temporalmente los valores que nos interesan. El problema se podría resolver así: // 1- el código comienza en p0=0, p1=0 y p2=0 // efectuamos la primera operación: mode #int set0 #2 set1 #3 suma // 2- tenemos el resultado de la suma contenido en reg2 p2 0 // seguimos con la siguiente operación tras cambiar de posición p2: p2 #1 set0 #3 set1 #2 resta // 3- ahora deberemos asignar los dos resultados a los dos registros principales para operar // los resultados intermedios están contenidos den core_reg2 p2 0 y core_reg2 p2 1: set1 core_reg2 p2 #0 set0 core_reg2 suma // 4- listo, ahora tenemos el resultado final en core_reg2 p2 0, podemos escribirlo o usarlo: busout #3,core_reg2 // 5- hemos modificado varios registros de la cadena reg2, a veces necesitamos estar seguros de // que una cadena de registros está vacía para no generar errores, para eso usamos flashX o flash flash Esta vez hemos terminado usando flash (también existe flash0, flash1 y flash2), ésta órden símplemente limpia toda la cadena de registros, así no tendremos sorpresas inesperadas al continuar con el código puesto que nos aseguramos de que tenemos la memoria limpia. Existen 6 instrucciones importantes para manejar los registros genearles, éstos son: (X significa un identificador de cadena de registros, 0 para reg0, 1 para reg1 y 2 para reg2) flash para vaciar todos los registros de reg0, reg1 y reg2 a la vez flashX vacía todos los registros de la cadena de registros regX setX asigna un valor al registro señalado por regX setX+ asigna valores múltiples separados por coma, uno en cada pX de regX pX cambia la dirección a la que apunta el puntero (pX) de regX mode cambia el modo en el que se operan los datos de todos los registros modeX cambia el modo en el que se operan los datos del registro regX También existen varias palabras reservadas para leer datos y otra información de la estructura de registros generales, estas son las más importantes: core_regX devuelve el valor contenido en regX en el registro apuntado por pX core_pX devuelve un número que indica la posición actual del puntero de regX (pX) core_numX devuelve un número que representa la cantidad de objetos no nulos dentro de regX core_joinX devuelve una cadena de texto con todos los registros de regX concatenados Haxbox es una máquina virtual capaz de interpretar código hax (o jenoassembler-hax), el cual está basado en la máquina virtual jénova, diseñada para correr sobre mundos de realidad virtual (opensim). Sus instrucciones básicas son compatibles con jénova, pero dispone de una estructura más completa así como de ciertas instrucciones especiales que iremos viendo. Es costumbre comenzar con un simple código de escritura siempre que se presenta un lenguaje de programación, el clásico "hola mundo". Así que sin entrar en detalles sobre su funcionamiento aquí está un simple hola mundo en lenguaje hax. // -hola mundo jahax busout #3,#Hola mundo! return Como se puede apreciar, a pesar de tratarse de un lenguaje ensamblador simbólico dispone de una codificación bastante reducida, no es necesario añadir metadatos ni tampoco cabeceras especiales, lo cual lo transforma en un ensamblador muy peculiar y versátil. En este ejemplo no hemos tenido la necesidad de usar ningún registro ni bloque de memoria, tan sólo dos parámetros estáticos, los cuales son creados, usados y destruidos. Busout es la instrucción que usa los parámetros, sirve para enviar mensajes a los módulos periféricos de haxbox (llamados en ocasiones satelites), cada satelite tiene un protocolo especial y realiza diferentes funciones, en este caso nos comunicamos con el satelite número 3, hablaremos de ello más adelante. Tan sólo saber que la manera de acceder a la estructura de salida es la instrucción busout, que puede tener dos o tres parámetros, dependiendo del caso. reg0, reg1 y reg2 son listas de registros, los resultados de operaciones se guardan en reg2
busout comunica con los satelites flags es una lista de registros para leer información opcional memscript contiene el código que se va a ejecutar mempar contiene las variables en parejas nombre-valor Haxbox es un intérprete de máquina virtual abstracta, una herramienta de programación con un lenguaje especial, que toma parte de los ensambladores mnemónicos y parte de los lenguajes de nivel alto. Tiene muchas peculiaridades que lo distinguen de otros sistemas de máquina virtual existentes, como por ejemplo java, tampoco emula la arquitectura x86 ni ninguna otra, se trata de una arquitectura propia diseñada especialmente para facilitar la flexibilidad, abstracción y legibilidad al máximo, así como optimizar la capacidad de compartir y editar los códigos.
Un procesador común tiene un tipo de datos básico en el que funciona, el byte, es decir tanto las instrucciones como los parámetros de éstas son transformadas a largas cadenas de bytes a la hora de trabajar con los datos. Haxbox utiliza el tipo de cadena de texto como unidad mínima, éstas cadenas pueden ser transformadas a números o a interrupciones boleanas en tiempo de ejecución. Ésto permite la lectura del código en cualquier momento, así como la edición de la memoria. Haxbox no es multihilo, ésto no es un punto a favor, pero facilita la comprensión de los códigos así como su diseño. Haxbox está escrito en lenguaje .net, se podría entender como un interface que permite manejar una serie de objetos .net de un modo alternativo. Así pues un script en lenguaje hax lee las instrucciones almacenadas en su memoria y las transforma en interacciones con varios objetos separados en módulos. Un objeto es la consola de texto plano, otro para escribir o leer archivos, otra reproduce el sonido indicado, otra es una superficie donde reproducir gráficos gdi, otra ejecuta comandos de la consola de windows (cmd.exe) y devuelve opcionalmente la salida, otra hace peticiones tcp y otra hace peticiones web usando el componente iexplorer y captura la respuesta. Diseñar un programa manejando tantos métodos y objetos usando .net es tedioso, pero con haxbox en unas pocas líneas de texto plano podremos conseguir un resultado decente. El lenguaje hax no tiene demasiadas instrucciones y su sintaxis es sencilla está basada en ensambladores mnemónicos. A pesar de asemejarse a un código ensamblador se trata de un lenguaje muy diferente con instrucciones que disparan procesos muy complejos y partes muy abstractas. El parecido proviene del hecho de que las instrucciones actuan diréctamente sobre la máquina, en este caso virtual, que dispone de registros y memorias al igual que un cpu físico. Eso si, haxbox mantiene un nivel de abstracción mucho más alto en el propio diseño de dichos componentes, haciendo que no debamos manejar complejas cadenas de bytes. Haxbox usa tipos de datos, pero éstos sólamente afectan en el momento de realizar operaciones y comparaciones aritméticas, el resto del tiempo se considera un objeto variable y su tipo sólo dependerá de dónde lo usemos. Las memorias están compuestas de registros que contienen cada uno una cadena de texto de longitud variable, tanto las memorias de programa como las de acceso aleatorio. Haxbox dispone de una arquitectura interna y otra externa. Llamamos interna a todo lo que incluye a los registros, la memoria del programa, las de acceso aleatorio, el contador, la caché etc y externa a los dispositivos virtuales de entrada-salida, la consola de texto plano, la superficie gráfica, el sonido, el sistema de archivos, el protocolo tcp etc... Para comunicarnos con toda esta estructura usamos sólamente una instrucción, "busout" que tiene 3 parámetros, el primero indica a qué componente enviar los datos, el segundo y el tercero son los datos que enviaremos. Cada dispositivo dispone de un protocolo propio, en ocasiones muy simple, como la consola de texto plano (que tan sólo escribe todo lo que llega en el primer dato) y otras algo más complejo (como la superficie gráfica). El resto de instrucciones interactuan con la arquitectura interna de la máquina y se usarán tanto para efectuar operaciones como para controlar el flujo del programa. Conjunto de instrucciones:
// comentario @ etiqueta label nop no ejecuta ninguna operación, a excepción de las operaciones laterales de los parámetros (como por ejemplo core_c+) flash vaciar todos los casilleros de los tres registros generales flash0 vaciar todos los casilleros de reg0 flash1 vaciar todos los casilleros de reg1 flash2 vaciar todos los casilleros de reg2 set0 asignar valor al casillero 0 set1 asignar valor al casillero 1 set2 asignar valor al casillero 2 set0+ asignar valores múltiples desde el casillero 0 actual ocupando los siguientes bloques set1+ asignar valores múltiples desde el casillero 1 actual ocupando los siguientes bloques set2+ asignar valores múltiples desde el casillero 2 actual ocupando los siguientes bloques p0 cambiar el número de casillero 0 p1 cambiar el número de casillero 1 p2 cambiar el número de casillero 2 mode0 cambiar el tipo de dato en casillero 0 mode1 cambiar el tipo de dato en casillero 1 mode2 cambiar el tipo de dato en casillero 2 mode cambiar el tipo de datos de los tres registros cachesave0 guardar todos los casilleros del registro 0 en el bloque caché indicado cachesave1 guardar todos los casilleros del registro 1 en el bloque caché indicado cachesave2 guardar todos los casilleros del registro 2 en el bloque caché indicado cacheload0 recuperar el bloque de caché indicado en el registro 0 cacheload1 recuperar el bloque de caché indicado en el registro 1 cacheload2 recuperar el bloque de caché indicado en el registro 2 parse0 dividir el valor del casillero 0 usando el caracter indicado como separador parse1 dividir el valor del casillero 1 usando el caracter indicado como separador parse2 dividir el valor del casillero 2 usando el caracter indicado como separador explode0 separar cada carácter de texto del registro 0 en un casillero de dicho registro explode1 separar cada carácter de texto del registro 1 en un casillero de dicho registro explode2 separar cada carácter de texto del registro 2 en un casillero de dicho registro tobyte0 transforma todos los casilleros de texto de reg0 en su equivalente en bytes tobyte1 transforma todos los casilleros de texto de reg1 en su equivalente en bytes tobyte2 transforma todos los casilleros de texto de reg2 en su equivalente en bytes todata0 transforma todos los casilleros de bytes de reg0 en su equivalente en texto todata1 transforma todos los casilleros de bytes de reg1 en su equivalente en texto todata2 transforma todos los casilleros de bytes de reg2 en su equivalente en texto input para la ejecución hasta que el usuario introduzca datos busout envía la pareja de datos indicada a través del bus requerido set asigna valor a una palabra clave en la memoria de pares o crea una nueva get sitúa en reg2 el valor de la palabra clave requerida set+ guarda como lista todos los valores indicados en los parámetros get+ recupera todos los valores de la lista indicada en reg2 unset elimina la palabra clave indicada y su valor de la memoria de pares debug escribe el texto indicado en la consola de debug jump salta hasta la etiqueta indicada jif_match salta hasta la etiqueta indicada si reg0 es igual a reg1 jif_max salta hasta la etiqueta indicada si reg0 es mayor que reg1 jif_less salta hasta la etiqueta indicada si reg0 es menor que reg1 jif salta hasta la etiqueta indicada si reg0 es igual que el valor true o 1 call llama al procedimiento de usuario indicado return termina el procedimiento de usuario actual y vuelve al lugar desde el que fue llamado hardstop para definitivamente la ejecución de la máquina pause para la ejecución de la máquina hasta recibir la señal de control run while inicia el modo bucle mientras la variable introducida sea true booleano loop indica el final de un bucle while, saltará hasta el inicio solo si se cumple la condición foreach inicia el modo bucle para cada uno de los casilleros de reg0 next pasa al siguiente casillero de reg0 para continuar con el bucle suma suma reg1 a reg0 y lo guarda en reg2, si uno de los dos registros es str concatenará en lugar de sumar resta resta reg1 a reg0 y lo guarda en reg2 mult multiplica reg0 y reg1 y lo guarda en reg2 div divide reg0 entre reg1 y lo guarda en reg2 rand genera un número aleatorio comprendido entre reg0 y reg1 and efectua la operación boleana and sobre reg0 y reg1 y guarda el resultado en reg2 or efectua la operación boleana or sobre reg0 y reg1 y guarda el resultado en reg2 not guarda en reg2 el valor boleano contrario a reg1 // :: obsoleto :: // setc definir el valor numérico del contador csum sumar el valor al contador csub restar el valor del contador minc definir valor mínimo del contador en modo loop maxc definir valor máximo del contador en modo loop loopc activar (1) o desactivar (0) el contador en modo loop // :: obsoleto :: // Los llamados satelites son periféricos virtuales de haxbox con los que se puede comunicar usando la instrucción busout. Cada uno de ellos tiene una función y un protocolo determinados, así como un número identificador:
Estos son unos simples ejemplos de uso del lenguaje jenoassembler-hax que pueden disipar alguna duda sobre el mismo: suma de valores introducidos por el usuario // sumar dos números introducidos call frame.prompt,#introduce número 1 set #num1,core_reg2 call frame.prompt,#introduce número 2 set #num2,core_reg2 mode #int set0 num1 set1 num2 suma call frame.print,core_reg2 return capturar teclas pulsadas y escribirlas en pantalla // test de captura de teclas busout 8,start @ rst call frame.printinln,surface_key jump @ rst // fin bucles foreach y while // test bucles busout 8,start flash0 set0+ hola,mundo,entero, p0 0 foreach call frame.print,core_reg0 next flash0 set0+ adios,cabeza,melon p0 0 foreach call frame.print,core_reg0 next setc 5 while core_c- call frame.printinln,#instrucciones iterativas!- call frame.print,core_c loop return sufijo post-suma // sumar 1 automáticamente a test al consultarlo y lo escribe en consola mode int set test,10 while 1 call frame.print,test++ loop cambiar color de fondo de surface // cambia constantemente el color del fondo busout 8,start @ start busout #8,#background,#green call frame.print,#green sleep #1 busout #8,#background,#blue call frame.print,#blue sleep #1 busout #8,#background,#red call frame.print,#red sleep #1 jump @ start Frame.hax es un archivo pensado para ser cargado a la memoria de haxbox justo al iniciar. Debería incluir los procedimientos más usados escritos en jenoassembler-hax para que cualquiera de nuestros programas pueda usarlos. Se aconseja mucho el uso de éstas funciones con su mismo nombre si se quiere mantener compatibilidad con los códigos de este sitio. Éste es el frame.hax hoy día. // frame.hax para haxbox 0.05
busout 8,start call frame.print,#*********************************** /n call frame.print,#**** maquina virtual haxbox. **** /n call frame.print,#*********************************** /n /n call frame.print,#2012 abdlab software/n/n/n/n set #_%ilasmexe%_,#C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe jump @ haxbox_frame_fin // :: haxboxframework V.1.0 :: proc frame.print busout #3,f!1 busout #3,/n return proc frame.printinln busout #3,f!1 return proc frame.printlst get+ f!1 set0+ core_reg2 foreach call frame.print,core_reg0 next return proc frame.prompt busout #3,f!1 busout #3,/n input busout #3,core_reg2 busout #3,/n return core_reg2 proc frame.rand set #_nums0,f!1 set #_nums1,f!2 mode int set0 _nums0 set1 _nums1 rand set #?return,core_reg2 mode str return core_reg2 proc frame.il.make set #_tmp,f!1 call frame.concat,#_%ilasmexe%_,_tmp call frame.shell,core_reg2 return f!0 proc frame.shell busout #10,f!1 return f!0 proc frame.web busout #12,f!1 return f!0 proc frame.telnet busout #11,f!1,f!2 return f!0 proc frame.nav.open set _tmp,f!1 call frame.concat,#start ,_tmp busout #10,core_reg2 return proc frame.savefile busout #5,f!1,f!2 return proc frame.readfile busout #6,f!1 return f!0 proc frame.concat mode str set0 f!1 set1 f!2 suma return core_reg2 proc frame.file.del mode str set0 #del set1 f!1 suma busout #10,core_reg2 return proc frame.bytecrypt set0 f!1 explode0 tobyte0 join0 #x return core_reg0 proc frame.bytedecrypt set0 f!1 parse0 #x todata0 set0 core_dump0 return core_reg0 proc frame.crypt set #_pass,f!2 set0 f!1 explode0 tobyte0 set #_nums0,core_num0 set #_nums1,_nums0 join0 #x return core_reg0 proc frame.decrypt set0 f!1 parse0 #x todata0 set0 core_dump0 return core_reg0 @ haxbox_frame_fin call frame.print,#$cls |