compudanzas/src/tutorial_de_uxn_día_1.gmo

616 lines
25 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tutorial uxn: día 1, aspectos básicos
lang=es en->{uxn tutorial day 1}
¡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 qué 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?
> El ecosistema de Uxn es un espacio de juego y exploración de computación personal, creado para desarrollar y utilizar pequeñas herramientas y juegos y programable en su propio lenguaje ensamblador.
=> https://100r.co/site/uxn.html 100R - uxn
te invito a leer "why create a smol virtual computer" ("por qué crear una pequeña computadora virtual") del sitio de 100R, para que conozcas más sobre la historia del proyecto.
uxn es el núcleo de la computadora virtual 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, conforme a la escala humana
* hecha para aplicaciones audiovisuales interactivas
* arquitectura y conjunto de instrucciones simple (¡solo 32 instrucciones!)
* primero-offline: funciona localmente y solo 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 conceptos suenan genial para mí y espero que para ti 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) y 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", donde las operaciones son indicadas mediante la llamada notación postfija.
> Notación Polaca Inversa (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 dos 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 a la pila
* el número 48 es empujado a la pila
* + toma dos elementos de la parte superior de la pila, los suma y empuja el resultado a la pila
el libro Starting Forth tiene algunas buenas ilustraciones sobre este proceso de suma:
=> https://www.forth.com/starting-forth/1-forth-stacks-dictionary/#The_Stack_Forth8217s_Workspace_for_Arithmetic The Stack: Forths Workspace for Arithmetic (Inglés)
## 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 estas expresiones funcionan y son equivalentes! solo 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 a 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 o resultados intermedios sin necesidad de que asignemos explícitamente 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 que 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 betabel o 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 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 secciones (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. ambos pueden ser accedidos aleatoriamente.
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 o las pilas 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
## instalación local
¿liste? para ejecutar varvara localmente y fuera de la red, tendríamos que obtener el ensamblador uxn (uxnasm) y el emulador (uxnemu) de su repositorio git:
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn - sourcehut git
puedes construir estas herramientas desde el código fuente o descargar binarios precompilados para múltiples plataformas.
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
```
o 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 fue bien, 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
* F4 carga una rom de inicio (launcher.rom) que permite navegar y abrir roms en el directorio actual
## 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
en principio puedes hacer doble clic en uxnemu y que se ejecute. sin embargo, utilizaremos estos programas desde la línea de comandos.
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/old/ :
ensambla darena.tal en darena.rom:
```
$ ./bin/uxnasm projects/examples/demos/darena.tal bin/darena.rom
```
corre 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).
que uxntal sea un lenguaje "ensamblado" 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 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 o los resultados que empuja de vuelta 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 de vuelta 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: es como si trasladáramos el operador de entre los operandos, hacia el final, después del segundo operando.
## 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
```
se abrirá una ventana negra, y en la consola veremos una salida parecida a la siguiente:
```
Assembled bin/hola.rom in 5 bytes(0.40% used), 0 labels, 0 macros.
Loaded hello.rom
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 línea 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 salte ese byte
* 68: un número hexadecimal, que corresponde al código ascii del carácter '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 estándar) y que la columna con la dirección 8 corresponde al puerto "escritura".
=> https://wiki.xxiivv.com/site/varvara.html varvara
así que, el dispositivo 18 corresponde a "escribir en consola", o salida estándar.
¡nuestro programa está enviando el valor hexadecimal 68 (carácter 'h') a la salida estándar!
puedes ver los valores hexadecimales de los caracteres ascii en la siguiente tabla:
=> https://wiki.xxiivv.com/site/ascii.html tabla ascii
### números literales
observe que los números literales que escribimos, 0100, 18 y 68, se escriben en hexadecimal utilizando 4 dígitos correspondientes a dos bytes, o 2 dígitos correspondientes a un byte.
en uxntal solo podemos escribir números de 2 ó 4 dígitos hexadecimales. si, por ejemplo, solo nos interesara escribir un único dígito hexadecimal, tendríamos que incluir un 0 a su izquierda.
## 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.
```
podemos confirmarlo usando el programa wc (word count):
```
$ wc -c hola.rom
5 hola.rom
```
para le curiose (¡como tú!), podemos usar una herramienta como hexdump para ver sus contenidos:
```
$ hexdump -C 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!
¡o sea, 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 ( nuevalínea )
```
si lo ensamblamos y corremos, tendremos un 'hola' en nuestra terminal, usando 25 bytes de programa :)
¿ok, y... te gusta? ¿parece sencillo? ¿quizás innecesariamente complejo?
veremos algunas características de uxntal que hacen que escribir y leer su código sea más cómodo.
# runas, etiquetas, macros
las runas son caracteres especiales que indican a uxnasm algún preprocesamiento a hacer al ensamblar nuestro programa.
## runa de pad absoluto
ya vimos la primera de ellas: | define un pad absoluto, o sea, 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 del 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 runa: #
este carácter 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
```
el siguiente tendría el mismo comportamiento que el programa de arriba, pero usando un byte menos (en el siguiente día veremos por qué):
```
( hola.tal )
|0100 #6818 DEO
```
nota que solo puedes usar esta runa para escribir los contenidos de uno o dos bytes (dos o cuatro nibbles).
importante: recuerda que esta runa (y las otras con la palabra "literal" en su nombre) es un atajo para la instrucción LIT. esto implica que uxn empujará estos valores hacia abajo en la pila. esto puede prestarse a confusión en algunos casos :)
si solo queremos tener un número específico en la memoria principal, sin empujarlo a la pila, simplemente escribiríamos el número tal cual, "crudo". esta es la forma en que lo hicimos en nuestros primeros programas de arriba.
## runa de carácter crudo o "raw"
esta es la runa de caracteres crudos: "
nos permite que uxnasm decodifique el valor numérico de un carácter 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 ( nuevalínea )
```
el "crudo" en el nombre de esta runa indica que no es literal, por ejemplo que no agrega una instrucción LIT.
por eso debemos incluir una instrucción LIT.
## runas para etiquetas
incluso ahora que sabemos que #18 corresponde a empujar a la pila la dirección de dispositivo del dispositivo para escribir en consola, 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 subdirecciones.
la runa @ nos permite definir etiquetas y la runa & nos permite definir subetiquetas.
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 @Consola [ &vector $2 &lee $1 &pad $5 &escribe $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 solo byte, uxnasm asume que es para el espacio de memoria de entrada/salida o la página cero.
luego vemos la etiqueta @Consola: esta etiqueta va a corresponder a la dirección 10.
los corchetes son ignorados, pero incluidos por legibilidad.
luego tenemos varias subetiquetas, indicadas por la runa &, y pads relativos, indicados por la runa $. ¿cómo los leemos/interpretamos?
* la subetiqueta &vector tiene la misma dirección que su etiqueta madre @Consola: 10
* $2 salta dos bytes (podemos leer esto como &vector siendo una dirección a una palabra de dos bytes de longitud)
* la subetiqueta &lee tiene la dirección 12
* $1 salta un byte (&lee sería una dirección para una palabra de 1 byte de longitud)
* la subetiqueta &pad tiene la dirección 13
* $5 salta el resto de los bytes del primer grupo de 8 bytes del dispositivo: estos bytes corresponden a las "entradas"
* la subetiqueta &escribe tiene la dirección 18 (¡la que ya conocíamos!)
* $1 salta un byte (&escribe 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 subetiquetas.
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 )
( dispositivos )
|10 @Consola [ &vector $2 &lee $1 &pad $5 &escribe $1 &error $1 ]
( programa principal )
|0100 LIT "h .Consola/escribe DEO
LIT "o .Consola/escribe DEO
LIT "l .Consola/escribe DEO
LIT "a .Consola/escribe DEO
#0a .Consola/escribe DEO ( nuevalínea )
```
ahora esto empieza a parecerse más a los ejemplos que puedes encontrar en línea 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.
```
.Consola/escribe DEO ( equivalente a #18 DEO, o a LIT 18 DEO )
```
podemos definir un macro llamado EMIT que tomará de la pila un byte correspondiente a un carácter y lo imprimirá en la salida estándar. para esto, necesitamos la runa % y llaves para la definición.
¡no olvides los espacios!
```
( escribe un carácter a la salida estándar )
%EMIT { .Consola/escribe DEO } ( carácter -- )
```
para llamar a un macro, solo escribimos su nombre:
```
( imprime carácter h )
LIT "h EMIT
```
podemos llamar a 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 @Consola [ &vector $2 &lee $1 &pad $5 &escribe $1 &error $1 ]
( macros )
( imprime un carácter en la salida estándar )
%EMIT { .Consola/escribe DEO } ( carácter -- )
( 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 bucle 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 carácter a la pila.
¿cómo reescribirías el programa para que primero empuje todos los caracteres y luego los emita (o haga "EMIT") todos con una secuencia como esta?
```
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-DÍGITO que toma un número (del 0 al 9) de la pila, e imprime el correspondiente dígito en la salida estándar.
```
%IMPRIMIR-DÍGITO { } ( número -- )
```
recordemos que el número tendría que ser escrito como un byte completo para que sea uxntal válido. si quisieras probar esta macro con, por ejemplo, el número 2, tendrías que escribirlo como 02:
```
#02 IMPRIMIR-DÍGITO
```
# instrucciones del día 1
estas 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
¡bien 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 él!
¡sin embargo, te invito a tomar un pequeño descanso antes de continuar! :)
# apoyo
si este tutorial te ha resultado de ayuda, considera compartirlo y brindarle tu {apoyo} :)