04 marzo, 2012

El intérprete Haxbox, introducción


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. Funciona sobre windows con .net framework 4.0 o superior (solo ha sido probado en W7).

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.

Instrucciones de la máquina virtual (hasta ahora):
  • // comentario
  • @ etiqueta label

  • 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
  • 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

  • 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
  • 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
  • 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
  • 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
  • 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
Publicar un comentario