602 lines
23 KiB
Plaintext
602 lines
23 KiB
Plaintext
# tutorial uxn: día 1, aspectos básicos
|
||
|
||
hola! en esta primera sección del {tutorial de uxn} vamos a hablar de los aspectos básicos de la computadora uxn llamada varvara, su paradigma de programación en un lenguaje llamado uxntal, su arquitectura, y por que puede que quieras aprender a programarla.
|
||
|
||
también saltaremos directo a nuestros primeros programas simples para demostrar conceptos fundamentales que desarrollaremos en los próximos días.
|
||
|
||
# ¿por qué uxn?
|
||
|
||
o primero que nada... ¿qué es uxn?
|
||
|
||
> Uxn es una computadora portable de 8 bit capaz de correr herramientas y juegos simples programable en su propio pequeño lenguaje ensamblador. Es también un ámbito de juego donde aprender habilidades básicas de computación.
|
||
|
||
=> https://wiki.xxiivv.com/site/uxn.html XXIIVV - uxn
|
||
|
||
te invito a leer "why create a smol virtual computer" del sitio de 100R, también:
|
||
|
||
=> https://100r.co/site/uxn.html 100R - uxn
|
||
|
||
uxn es el núcleo de la computadora virtual (¿por el momento?) varvara. es lo suficientemente simple como para ser emulada por diversas plataformas de computación viejas y nuevas y ser seguida a mano.
|
||
|
||
personalmente, veo en ella las siguientes virtudes:
|
||
|
||
* hecha para el largo plazo
|
||
* hecha para aplicaciones audiovisuales interactivas
|
||
* arquitectura y set de instrucciones simple (¡sólo 32 instrucciones!)
|
||
* primero-offline: funciona localmente y sólo necesitas unos pocos archivos de documentación para avanzar
|
||
* ámbito de práctica y experimentación de computación dentro de límites
|
||
* ya portada a plataformas de computación actuales y de varios años de antigüedad
|
||
|
||
¡todos estos conceotos suenan genial para mí, y espero que para tí también! sin embargo, noto algunos aspectos que pueden hacerla parecer no tan asequible:
|
||
|
||
* es programada en un lenguaje ensamblador, uxntal
|
||
* utiliza notación {postfix} (alias notación polaca inversa) / está inspirada en máquinas forth
|
||
|
||
la idea de este tutorial es explorar estos dos aspectos y revelar cómo trabajan juntos para dar a uxn su poder con una complejidad relativamente baja.
|
||
|
||
# notación postfija (y la pila)
|
||
|
||
el núcleo uxn está inspirado por las máquinas forth en que utiliza la recombinación de componentes simples para lograr soluciones apropiadas, y en que es una máquina basada en pila.
|
||
|
||
esto implica que está principalmente basada en interacciones con una pila "push down", dónde las operaciines son indicadas mediante la llamada notación postfija.
|
||
|
||
> Notación Polaca Reversa (NPR), también conocida como notación postfija polaca o simplemente notación postfija, es una notación matemática en la que los operadores siguen a sus operandos [...]
|
||
|
||
=> https://es.wikipedia.org/wiki/Notaci%C3%B3n_polaca_inversa Notación Polaca Inversa - Wikipedia
|
||
|
||
## suma postfija
|
||
|
||
en notación postfija, la suma de nos números sería escrita de la siguiente forma:
|
||
|
||
``` 1 48 +
|
||
1 48 +
|
||
```
|
||
|
||
dónde, leyendo de izquierda a derecha:
|
||
|
||
* el número 1 es empujado en la pila
|
||
* el número 48 es empujado en la pila
|
||
* + toma dos elementos de la parte superior de la pila, los suma, y empuja el resultado en la pila
|
||
|
||
el libro Starting Forth tiene algunas ilustraciones grandiosas sobre este proceso de suma:
|
||
|
||
=> https://www.forth.com/starting-forth/1-forth-stacks-dictionary/#The_Stack_Forth8217s_Workspace_for_Arithmetic The Stack: Forth’s Workspace for Arithmetic
|
||
|
||
## de infija a postfija
|
||
|
||
expresiones más complejas en notación infija, que requieren paréntesis o reglas de precedencia de operadores (y un sistema más complejo para codificarlas), pueden ser simplificadas mediante notación postfija.
|
||
|
||
por ejemplo, la siguiente expresión infija:
|
||
|
||
``` (2 + 16)/8 + 48
|
||
(3 + 5)/2 + 48
|
||
```
|
||
|
||
puede ser escrita en notación postfija como:
|
||
|
||
``` 3 5 + 2 / 48 +
|
||
3 5 + 2 / 48 +
|
||
```
|
||
|
||
también podemos escribirla de muchas otras maneras, por ejemplo:
|
||
|
||
``` 48 2 3 5 + / +
|
||
48 3 5 + 2 / +
|
||
```
|
||
|
||
¡asegúrate de que éstas expresiones funcionan y son equivalentes! sólo debes seguir estas reglas, leyendo de izquierda a derecha:
|
||
|
||
* si es un número, empujarlo a la pila
|
||
* si es un operador, tomar dos elementos de la parte superior de la pila, aplicar la operación, y empujar el resultado en la pila.
|
||
|
||
nota: en el caso de la división, los operandos siguen el mismo orden de izquierda a derecha. 3/2 sería escrito como:
|
||
|
||
``` 3 2 /
|
||
3 2 /
|
||
```
|
||
|
||
irás descubriendo cómo el uso de la pila puede ser muy poderoso ya que ahorra operandos y/o resultados intermedios sin necesidad de que asignemos explicitamente un espacio en memoria para ellos (por ejemplo, mediante el uso de "variables" en otros lenguajes de programación)
|
||
|
||
¡vamos a volver a la notación postfija y la pila muy pronto!
|
||
|
||
# arquitectura de la computadora varvara
|
||
|
||
una de las cuestiones de programar una computadora a un nivel bajo de abstracción, como estaremos haciendo con uxn, es que tenemos que conocer y estar atentos a sus funcionamientos internos.
|
||
|
||
## 8-bits y hexadecimal
|
||
|
||
las palabras binarias de 8 bits, también conocidas como bytes, son los elementos básicos de codificación y manipulación de datos en uxn.
|
||
|
||
uxn puede manejar también palabras binarias de 16 bits (2 bytes), también conocidas como cortos, mediante la concatenación de dos bytes consecutivos. vamos a hablar más sobre esto en el segundo día de este tutorial.
|
||
|
||
los números en uxn son expresados utilizando el sistema hexadecimal (base 16), en el qhe cada dígito (nibble) va de 0 a 9 y luego de 'a' a 'f' (en minúscula).
|
||
|
||
un byte necesita dos dígitos hexadecimales (nibbles) para ser expresado, y un corto necesita cuatro.
|
||
|
||
## el cpu uxn
|
||
|
||
se ha dicho que el cpu uxn es una remolacha, capaz de ejecutar 32 instrucciones diferentes con tres banderas de modo diferentes.
|
||
|
||
cada instrucción junto con sus banderas de modo puede ser codificada en una sola palabra de 8 bits.
|
||
|
||
todas estas instrucciones operan sobre elementos en la pila, ya sea tomando de ella sus operandos y/o empujando en ella sus resultados.
|
||
|
||
vamos a ir cubriendo lentamente estas instrucciones durante este tutorial.
|
||
|
||
## memoria uxn
|
||
|
||
la memoria en la computadora uxn consiste en cuatro espacios separados:
|
||
|
||
* memoria principal, con 65536 bytes
|
||
* memoria de entrada/salida, con 256 bytes dividida en 16 secciines (o dispositivos) de 16 bytes cada uno: 8 bytes para entradas y 8 bytes para salidas.
|
||
* pila de trabajo, con 256 bytes
|
||
* pila de retorno, con 256 bytes
|
||
|
||
cada byte en la memoria principal posee una dirección de 16 bits (2 bytes) de tamaño, mientras que cada byte en la memoria de entrada/salida posee una dirección de 8 bits (1 byte) de tamaño. ambas pueden ser accedidas de manera aleatoria.
|
||
|
||
los primeros 256 bytes de la memoria principal constituyen una sección llamada página cero. esta sección puede ser referenciada por 8 bits (1 byte), y su propósito es almacenar datos durante el tiempo de ejecución de la máquina.
|
||
|
||
hay tres instrucciones diferentes para interactuar con cada uno de estos espacios de memoria.
|
||
|
||
la memoria principal almacena el programa a ser ejecutado, empezando en el byte 257 (dirección 0100 en hexadecimal). también puede almacenar datos.
|
||
|
||
las pilas no pueden ser accedidas aleatoriamente; la máquina uxn se ocupa de ellas.
|
||
|
||
## ciclo de instrucción
|
||
|
||
el cpu uxn lee un byte por vez de la memoria principal.
|
||
|
||
el contador de programa es una palabra de 16 bits que indica la dirección del próximo byte a leer. su valor inicial es la dirección 0100 en hexadecimal.
|
||
|
||
una vez que el cpu lee un byte, lo decodifica como instrucción y lo ejecuta.
|
||
|
||
la instrucción va a implicar normalmente un cambio en la(s) pila(s), y algunas veces también un cambio en el flujo normal del contador de programa: en lugar de apuntar al siguiente byte en memoria, puede ser apuntado a otro lado, "saltando" de un lugar en memoria a otro.
|
||
|
||
# instalación y toolchain
|
||
|
||
¿liste? obtengamos el ensamblador uxn (uxnasm) y emulador (uxnemu) de su repositorio git:
|
||
|
||
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn - sourcehut git
|
||
|
||
estas instrucciones son para sistemas basados en linux.
|
||
|
||
si necesitas una mano, encuéntranos en #uxn en irc.esper.net :)
|
||
|
||
## instalar SDL2
|
||
|
||
para compilar uxnemu, necesitamos instalar la librería SDL2.
|
||
|
||
en una terminal en debian/ubuntu, correr:
|
||
|
||
``` sudo apt install libsdl2-dev
|
||
$ sudo apt install libsdl2-dev
|
||
```
|
||
|
||
on en guix:
|
||
|
||
``` guix install sdl2
|
||
$ guix install sdl2
|
||
```
|
||
|
||
## obtener y compilar uxn
|
||
|
||
obtengamos y compilemos uxnemu y uxnasm:
|
||
|
||
```
|
||
$ git clone https://git.sr.ht/~rabbits/uxn
|
||
$ cd uxn
|
||
$ ./build.sh
|
||
```
|
||
|
||
si todo fué bién, verás varios mensajes en la terminal y una pequeña ventana con el título uxn, y una aplicación demo: uxnemu está ahora corriendo una "rom" correspondiendo a esa aplicación.
|
||
|
||
## controles uxnemu
|
||
|
||
* F1 itera entre diferentes niveles de zoom
|
||
* F2 muestra el debugger en pantalla
|
||
* F3 toma una captura de pantalla de la ventana
|
||
|
||
## usando el toolchain
|
||
|
||
verás que luego de compilar uxn, cuentas con tres nuevos archivos ejecutables en el directorio bin/:
|
||
|
||
* uxnemu: el emulador
|
||
* uxnasm: el ensamblador
|
||
* uxncli: un emulador de consola no interactivo
|
||
|
||
puedes ajustar tu $PATH para tenerlos disponibles en todos lados.
|
||
|
||
la idea es que para correr un programa escrito en uxntal (el lenguaje ensamblador de uxn), primero tienes que ensamblarlo en una "rom", y luego puedes correr esta rom con el emulador.
|
||
|
||
por ejemplo, para correr {darena} que se encuentra en projects/examples/demos/ :
|
||
|
||
```
|
||
ensambla darena.tal en darena.rom
|
||
$ ./bin/uxnasm projects/examples/demos/darena.tal bin/darena.rom
|
||
|
||
correr darena.rom
|
||
$ ./bin/uxnemu bin/darena.rom
|
||
```
|
||
|
||
¡échale una mirada a los demos disponibles! (¡o no, y empecemos a programar los nuestros!)
|
||
|
||
# uxntal y un muy básico hola mundo
|
||
|
||
uxntal es el lenguaje ensamblador para la máquina uxn.
|
||
|
||
estuvimos hablando antes sobre el cpu uxn y las 32 instrucciones que sabe cómo ejecutar, cada una de ellas codificada como una sola palabra de 8 bits (byte).
|
||
|
||
ese lenguaje ensamblador uxntal implica que hay una relación uno a uno mapeando de una instrucción escrita en el lenguaje a una palabra de 8 bit correspondiente que el cpu puede interpretar.
|
||
|
||
por ejemplo, la instrucción ADD (suma) en uxntal es codificada como un byte con el valor 18 en hexadecimal, y corresponde al siguiente conjunto de de acciones: toma los dos elementos superiores de la pila, los suma, y empuja el resultado a la pila.
|
||
|
||
en sistemas de tipo forth podemos ver el siguiente tipo de notación para expresar los operandos que una instrucción toma de la pila, y el(los) resultado(s) que empuja devuelta a la pila:
|
||
|
||
```
|
||
ADD ( a b -- a+b )
|
||
```
|
||
|
||
esto significa que ADD toma el primer elemento desde arriba 'b', luego toma el siguiente primer elemento 'a', y empuja devuelta el resultado de sumar a+b.
|
||
|
||
ahora que estamos en eso, hay una instrucción complementaria, SUB (resta) (opcode 19), que toma los dos elementos superiores de la pila, los resta, y empuja a la pila el resultado:
|
||
|
||
```
|
||
SUB ( a b -- a-b )
|
||
```
|
||
|
||
nota que el orden de los operandos es similar al de la división que discutimos arriba cuando hablamos de notación postfija.
|
||
|
||
## un primer programa
|
||
|
||
escribamos el siguiente programa en nuestro editor de texto favorito, y guardémoslo como hola.tal:
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 LIT 68 LIT 18 DEO
|
||
```
|
||
|
||
ensamblémoslo y corrámoslo:
|
||
|
||
```
|
||
$ ./bin/uxnasm hola.tal bin/hola.rom && ./bin/uxnemu bin/hola.rom
|
||
```
|
||
|
||
veremos una salida con el siguiente aspecto:
|
||
|
||
```
|
||
Assembled bin/hola.rom(5 bytes), 0 labels, 0 macros.
|
||
Uxn loaded[bin/hola.rom].
|
||
Device added #00: system, at 0x0000
|
||
Device added #01: console, at 0x0010
|
||
Device added #02: screen, at 0x0020
|
||
Device added #03: audio0, at 0x0030
|
||
Device added #04: audio1, at 0x0040
|
||
Device added #05: audio2, at 0x0050
|
||
Device added #06: audio3, at 0x0060
|
||
Device added #07: ---, at 0x0070
|
||
Device added #08: controller, at 0x0080
|
||
Device added #09: mouse, at 0x0090
|
||
Device added #0a: file, at 0x00a0
|
||
Device added #0b: datetime, at 0x00b0
|
||
Device added #0c: ---, at 0x00c0
|
||
Device added #0d: ---, at 0x00d0
|
||
Device added #0e: ---, at 0x00e0
|
||
Device added #0f: ---, at 0x00f0
|
||
h
|
||
```
|
||
|
||
la última 'h' que vemos es la salida de nuestro programa. cambia el 68 a, por ejemplo, 65, y verás una 'e'.
|
||
|
||
¿qué es lo que está pasando?
|
||
|
||
## una instrucción por vez
|
||
|
||
acabamos de correr el siguiente programa en uxntal:
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 LIT 68 LIT 18 DEO
|
||
```
|
||
|
||
la primera línea es un comentario: los comentarios son encerrados entre paréntesis y debe haber un espacio entre cada paréntesis y el contenido. de manera similar a otros lenguajes de programación, los comentarios son ignorados por el ensamblador.
|
||
|
||
en la segunda linea ocurren varias cosas:
|
||
|
||
* |0100 : puede que recuerdes este número de antes - este es el valor inicial del contador de programa; la dirección del primer byte que el cpu lee. usamos esta notación para indicar que lo que esté escrito después será escrito en memoria después de esta dirección.
|
||
* LIT : esta aparece dos veces, y es una instrucción uxn con las siguientes acciones: empuja el siguiente valor en memoria a la pila, y hace que el contador de programa saltee ese byte.
|
||
* 68 : un número hexadecimal, que corresponde al código ascii del caracter 'h'
|
||
* 18 : un número hexadecimal, que corresponde a una dirección de entrada/salida: dispositivo 1 (consola), dirección 8.
|
||
* DEO : otra instrucción uxn, que podemos definir como lo siguiente: escribir el byte dado en la dirección de dispositivo dada, ambos tomados de la pila ( byte dirección -- )
|
||
|
||
leyendo el programa de izquierda a derecha, podemos ver el siguiente comportamiento:
|
||
|
||
* la instrucción LIT empuja el número 68 a la pila
|
||
* la instrucción LIT empuja el número 18 a la pila
|
||
* la instrucción DEO toma el elemento superior de la pila (18) y lo usa como dirección de dispositivo
|
||
* la instrucción DEO toma el elemento superior de la pila (68) y lo usa como byte a escribir
|
||
* la instrucción DEO escribe el byte a la dirección de dispositivo, dejando la pila vacía
|
||
|
||
¿y qué es el dispositivo de entrada/salida con la dirección 18?
|
||
|
||
mirando en la tabla de dispositivos de la referencia varvara, podemos ver que el dispositivo con la dirección 1 en el nibble superior es la consola (entrada y salida estandard), y que la columna con la dirección 8 corresponde a "escritura".
|
||
|
||
=> https://wiki.xxiivv.com/site/uxnemu.html uxnemu
|
||
|
||
asique, el dispositivo 18 corresponde a "escribir en consola", o salida estandard.
|
||
|
||
¡nuestro programa está enviando el valor hexadecimal 68 (caracter 'h') a la salida estandard!
|
||
|
||
puedes ver los valores hexadecimales de los caracteres ascii en la siguiente tabla:
|
||
|
||
=> https://wiki.xxiivv.com/site/ascii.html ascii table
|
||
|
||
## rom ensamblada
|
||
|
||
podemos ver que el ensamblador reporta que nuestro programa es de 5 bytes de tamaño:
|
||
|
||
```
|
||
Assembled bin/hola.rom(5 bytes), 0 labels, 0 macros.
|
||
```
|
||
|
||
para el curioso (¡como tú!), podemos usar una herramienta como hexdump para ver sus contenidos:
|
||
|
||
```
|
||
$ hexdump -C bin/hola.rom
|
||
00000000 80 68 80 18 17 |.h...|
|
||
00000005
|
||
```
|
||
|
||
80 es el "opcode" correspondiente a LIT, y 17 es el opcode correspondiente a DEO. ¡y ahí están nuestros 68 y 18!
|
||
|
||
¡osea, efectivamente, nuestro programa ensamblado presenta una correspondencia uno a uno con las instrucciones que acabamos de escribir!
|
||
|
||
|
||
de hecho, podríamos haber escrito nuestro programa con estos números hexadecimales (el código máquina), y hubiera funcionado igual:
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 80 68 80 18 17 ( LIT 68 LIT 18 DEO )
|
||
```
|
||
|
||
tal vez no sea la manera más práctica de programar, pero ciertamente una divertida :)
|
||
|
||
puedes encontrar los opcodes de todas las 32 instrucciones en la referencia uxntal
|
||
|
||
=> https://wiki.xxiivv.com/site/uxntal.html XXIIVV - uxntal
|
||
|
||
## programa hola
|
||
|
||
podemos expandir nuestro programa para imprimir más caracteres:
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 LIT 68 LIT 18 DEO ( h )
|
||
LIT 6f LIT 18 DEO ( o )
|
||
LIT 6c LIT 18 DEO ( l )
|
||
LIT 61 LIT 18 DEO ( a )
|
||
LIT 0a LIT 18 DEO ( newline )
|
||
```
|
||
|
||
si lo ensamblamos y corremos, tendremos un 'hola' en nuestra terminal, usando 25 bytes de programa :)
|
||
|
||
¿ok, y... te gusta?
|
||
|
||
¿parece innecesariamente complejo?
|
||
|
||
veremos ahora algunas virtudes de uxntal que hacen escribir y leer código más "confortable".
|
||
|
||
# runas, etiquetas, macros
|
||
|
||
las runas son caracteres especiales que indican a uxnasm algún pre procesamiento a hacer al ensamblar nuestro programa.
|
||
|
||
## runa de pad absoluto
|
||
|
||
ya vimos la primera de ellas: | define un pad absoluto: la dirección donde el siguiente elemento escrito será ubicado en memoria.
|
||
|
||
si la dirección es de 1 byte de longitud, es asumido que es una dirección de el espacio de entrada/salida o de la página cero.
|
||
|
||
si la dirección es de 2 bytes de longitud, es asumido que es una dirección de la memoria principal.
|
||
|
||
## runa hex literal
|
||
|
||
hablemos de otra: #.
|
||
|
||
éste caracter define un "hex literal": es básicamente un atajo para la instrucción LIT.
|
||
|
||
usando esta runa, podemos reescribir nuestro primer programa como:
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 #68 #18 DEO
|
||
```
|
||
|
||
nota que sólo puedes usar esta runa para escribir los contenidos de uno o dos bytes (dos o cuatro nibbles).
|
||
|
||
el siguiente tendría el mismo comportamiento que el programa de arriba, pero usando un byte menos (en la siguiente sección/día veremos por qué)
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 #6818 DEO
|
||
```
|
||
|
||
importante: recuerda que esta runa (y las otras con la palabra "literal" en su nombre) es un atajo para la instrucción LIT. esto puede prestarse a confusión en algunos casos :)
|
||
|
||
## runa de caracter raw
|
||
|
||
ésta es la runa de caracter raw: '
|
||
|
||
nos permite que uxnasm decodifique el valor numérico de un caracter ascii.
|
||
|
||
nuestro "programa hola" luciría de la siguiente manera, usando las nuevas runas que acabamos de aprender:
|
||
|
||
```
|
||
( hola.tal )
|
||
|0100 LIT 'h #18 DEO
|
||
LIT 'o #18 DEO
|
||
LIT 'l #18 DEO
|
||
LIT 'a #18 DEO
|
||
#0a #18 DEO ( newline )
|
||
```
|
||
|
||
el "raw" en el nombre de esta runa indica que no es literal, por ejemplo que no agrega una instrucción LIT.
|
||
|
||
## runas para etiquetas
|
||
|
||
incluso ahora que sabemos que #18 corresponde a empujar la dirección de dispositivo escribir en consola en la pila, por legibilidad y para asegurar nuestro código a futuro es una buena práctica asignar una serie de etiquetas que corresponderán a ese dispositivo y sus sub direcciones.
|
||
|
||
la runa @ nos permite definir etiquetas, y la runa & nos permite definir sub etiquetas.
|
||
|
||
por ejemplo, para el dispositivo de consola, la manera en que verías esto escrito en programas para la computadora varvara es la siguiente:
|
||
|
||
```
|
||
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|
||
```
|
||
|
||
podemos ver un pad absoluto a la dirección 10, que asigna lo siguiente a esa dirección. dado que la dirección consiste en un sólo byte, uxnasm asume que es para el espacio de memoria de entrada/salida o la página cero.
|
||
|
||
luego vemos la etiqueta @Console: ésta etiqueta va a corresponder a la dirección 10.
|
||
|
||
los corchetes son ignorados, pero incluidos por legibilidad.
|
||
|
||
luego tenemos varias sub etiquetas, indicadas por la runa &, y pads relativos, indicados por la runa $. ¿cómo los leemos/interpretamos?
|
||
|
||
* la sub etiqueta &vector tiene la misma dirección que su etiqueta madre @Console: 10
|
||
* $2 salta dos bytes (podemos leer esto como &vector siendo una dirección a una palabra de dos bytes de longitud)
|
||
* la sub etiqueta &read tiene la dirección 12
|
||
* $1 salta un byte (&read sería una dirección para una palabra de 1 byte de longitud)
|
||
* la sub etiqueta &pad tiene la dirección 13
|
||
* $5 salta el resto de los bytes del primer grupo de 8 bytes del dispositivo: éstos bytes corresponden a las "entradas"
|
||
* la sub etiqueta &write tiene la dirección 18 (¡la que ya conocíamos!)
|
||
* $1 salta un byte (&write sería una dirección para una palabra de 1 byte de longitud)
|
||
* la subetiqueta &error tiene la dirección 19
|
||
|
||
nada de esto sería traducido a código máquina, pero nos asiste al escribir código uxntal.
|
||
|
||
la runa para referirse a una dirección literal en la página cero o el espacio de direcciones de entrada/salida, es . (punto), y una / (barra) nos permite referirnos a una de sus sub etiquetas.
|
||
|
||
recuerda: al ser una runa de "dirección literal" va a agregar una instrucción LIT antes de la correspondiente dirección :)
|
||
|
||
podemos reescribir nuestro "programa hola mundo" como sigue:
|
||
|
||
```
|
||
( hola.tal )
|
||
|
||
( devices )
|
||
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|
||
|
||
( programa principal )
|
||
|0100 LIT 'h .Console/write DEO
|
||
LIT 'o .Console/write DEO
|
||
LIT 'l .Console/write DEO
|
||
LIT 'a .Console/write DEO
|
||
#0a .Console/write DEO ( newline )
|
||
```
|
||
|
||
ahora esto empieza a parecerse más a los ejemplos que puedes encontrar en línea y/o en el repositorio uxn :)
|
||
|
||
## macros
|
||
|
||
siguiendo con la herencia de forth (?), en uxntal podemos definir nuestras propias "palabras" que nos permiten agrupar y reutilizar instrucciones.
|
||
|
||
durante el ensamblado, estos macros son (recursivamente) reemplazados por los contenidos de sus definiciones.
|
||
|
||
por ejemplo, podemos ver que el siguiente fragmento de código es repetido varias veces en nuestro programa.
|
||
|
||
```
|
||
.Console/write DEO ( equivalent to #18 DEO, or LIT 18 DEO )
|
||
```
|
||
|
||
podemos definir un macro llamado EMIT que tomará de la pila un byte correspondiente a un caracter, y lo imprimirá en la salida estandard. para esto, necesitamos la runa %, y llaves para la definición.
|
||
|
||
¡no olvides los espacios!
|
||
|
||
```
|
||
( escribe un caracter a la salida estandard )
|
||
%EMIT { .Console/write DEO } ( caracter -- )
|
||
```
|
||
|
||
para llamar a un macro, sólo escribimos su nombre:
|
||
|
||
```
|
||
( imprime caracter h )
|
||
LIT 'h EMIT
|
||
```
|
||
|
||
podemos llamar macros dentro de macros, por ejemplo:
|
||
|
||
```
|
||
( imprime una nueva línea )
|
||
%NL { #0a EMIT } ( -- )
|
||
```
|
||
|
||
# un hola mundo más idiomático
|
||
|
||
usando todos estos macros y runas, nuestro programa puede terminar luciendo como lo siguiente:
|
||
|
||
```
|
||
( hola.tal )
|
||
( dispositivos )
|
||
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|
||
|
||
( macros )
|
||
( imprime un caracter en la salida estandard )
|
||
%EMIT { .Console/write DEO } ( caracter -- )
|
||
( imprime una nueva línea )
|
||
%NL { #0a EMIT } ( -- )
|
||
|
||
( programa principal )
|
||
|0100 LIT 'h EMIT
|
||
LIT 'o EMIT
|
||
LIT 'l EMIT
|
||
LIT 'a EMIT
|
||
NL
|
||
```
|
||
|
||
termina siendo ensamblado en los mismos 25 bytes que los ejemplos de arriba, pero con suerte más legible y mantenible.
|
||
|
||
podemos "mejorar" este programa haciendo que un loop imprima los caracteres, pero estudiaremos eso más tarde :
|
||
|
||
# ejercicios
|
||
|
||
## reubicando EMIT
|
||
|
||
en nuestro programa previo, el macro EMIT es llamado justo después de empujar un caracter a la pila.
|
||
|
||
¿cómo reescribirías el programa para empujar primero todos los caracteres, y luego "EMIT"ir todos ellos en una secuencia como ésta?
|
||
|
||
```
|
||
EMIT EMIT EMIT EMIT EMIT
|
||
```
|
||
|
||
## imprimir un dígito
|
||
|
||
si miras en la tabla ascii, verás que el código hexadecimal 30 corresponde al dígito 0, 31 al dígito 1, siguiendo hasta el 39 que corresponde al dígito 9.
|
||
|
||
define un macro IMPRIMIR-DIGITO que toma un número (del 0 al 9) de la pila, e imprime el correspondiente dígito en la salida estandard.
|
||
|
||
```
|
||
%IMPRIMIR-DIGITO { } ( número -- )
|
||
```
|
||
|
||
# instrucciones del día 1
|
||
|
||
éstas son las instrucciones que cubrimos hoy:
|
||
|
||
* ADD: toma los dos elementos superiores de la pila, los suma, y empuja el resultado ( a b -- a+b )
|
||
* SUB: toma los dos elementos superiores de la pila, los resta, y empuja el resultado ( a b -- a-b )
|
||
* LIT: empuja el siguiente byte en memoria a la pila
|
||
* DEO: escribe el byte dado en la dirección de dispositivo dada, tomando ambos de la pila ( byte dirección -- )
|
||
|
||
# día 2
|
||
|
||
¡bién hecho!
|
||
|
||
en el {tutorial de uxn día 2} empezamos a explorar los aspectos visuales de la computadora varvara: ¡hablamos sobre los aspectos fundamentales del dispositivo de pantalla para que podamos empezar a dibujar en el!
|
||
|
||
¡sin embargo, te invito a tomar un pequeño descanzo antes de continuar! :)
|
||
|
||
# apoyo
|
||
|
||
si este tutorial te ha resultado de ayuda, considera compartirlo y brindarle tu {apoyo} :)
|
||
|