v32lua: Compilador de Lua para Vircon32

A Vircon32-assembly targeting lua compiler

v32lua: Compilador de Lua para Vircon32

Versión en inglés / in English Versión en francés / en français

Arquitectura Objetivo: Consola de Fantasía Vircon32 (32-bit)

Lenguaje de Implementación: C (Flex/Bison + Emisor Semántico Personalizado)

Repositorio: github.com/wedge1020/v32lua

Referencia de la API: doc/API.es.md — la API nativa completa de Vircon32 (sonido, gráficos, entrada, mapas de mosaicos, tarjeta de memoria y puertos de E/S en bruto). Empieza por su Referencia rápida: cada intrínseco y cada puerto ioports.* en un solo lugar.

Construir un cartucho: doc/USAGE.es.md — la línea de comandos, las pistas --#, el XML generado y las herramientas de Vircon32 que completan el trabajo.

v32lua es un compilador de Lua escrito en C que tiene como objetivo la consola de fantasía Vircon32. En lugar de incorporar un intérprete de bytecode pesado, v32lua analiza el código fuente de Lua y lo compila directamente a ensamblador nativo de Vircon32, además de producir la definición XML del cartucho que necesita la cadena de herramientas de la consola para empaquetar una ROM.

Aunque todavía no está completo, uno de los objetivos del desarrollo es convertir a v32lua en un sustituto (de ninguna manera un reemplazo) del Compilador de C de Vircon32 dentro de su cadena de desarrollo. Básicamente, elige tu lenguaje preferido — C o Lua — y, una vez compilado a ensamblador, continúas con la construcción sin importar el lenguaje de implementación. Como resultado, se ha hecho un esfuerzo por imitar varios comportamientos del Compilador de C de Vircon32 para que la sustitución del compilador sea más transparente.

Diseñado desde cero teniendo en cuenta las restricciones de una consola de fantasía retro, v32lua cuenta con intrínsecos de “hardware” de bajo nivel y costo cero, NaN-boxing personalizado y — más allá de la API nativa de Vircon32 — dos capas de compatibilidad de API para que los cartuchos escritos para TIC-80 y PICO-8 puedan compilarse y ejecutarse en el entorno de Vircon32 con poca o ninguna modificación del código fuente.

+------------------+     +-------------------+     +------------------+
| Fuente (.lua)    | --> | Lexer y Parser    | --> | Construcción AST |
+------------------+     | (Flex / Bison)    |     +------------------+
                         +-------------------+              |
                                                            v
+------------------+     +-------------------+     +------------------+
| Config. Cartucho | <-- | Emisor ensamblador| <-- | Emisor Semántico |
| (.xml)           |     | Vircon32 (.asm)   |     |                  |
+------------------+     +-------------------+     +------------------+

Tabla de Contenidos


Primeros Pasos

Requisitos

Compilando el Compilador

El repositorio incluye un Makefile en la raíz que gestiona la compilación del binario del compilador, la ejecución de la suite de pruebas y el mantenimiento general del proyecto. Para compilar el binario principal del compilador desde el código fuente, ejecuta el objetivo por defecto desde la raíz del repositorio:

make

Esto compila los distintos archivos fuente y produce el binario v32lua (bajo bin/), que convierte un archivo fuente .lua en un archivo .asm de Vircon32 más un .xml de cartucho que lo acompaña. A partir de ahí, ensamblar y empaquetar sigue los mismos pasos que cualquier otro proyecto de Vircon32 (ensamblar → packrom → ejecutar bajo v32sim o en el emulador oficial).

Tabla de Referencia de Objetivos del Makefile

Objetivo Descripción
all Objetivo por defecto. Compila el compilador (bin/v32lua) dentro de src/.
clean Elimina los artefactos de compilación de src/ y los archivos generados en testing/ y demos/.
install Copia bin/v32lua a ~/bin/bin.$(ARCH)/ si existe, si no a ~/bin/.
sysinstall Instalación en el sistema: bin/v32lua en /usr/local/bin/, la página de manual en /usr/local/share/man/man1/, la biblioteca de --#include (lib/*.lua) en /usr/local/Vircon32/v32tools/include/v32lua/ y los documentos en /usr/local/Vircon32/v32tools/docs/v32lua/ (normalmente requiere sudo; DEST_LANG= elige el idioma de los documentos y PREFIX= la ubicación: ver Instalación).
sysuninstall Elimina lo que instaló sysinstall.
tests Ejecuta las pruebas unitarias de testing/: cada programa se compila, se ensambla, se empaqueta y se ejecuta en v32sim, y sus resultados se comparan con el bloque EXPECTED OUTPUT del archivo (requiere las DevTools, v32sim y v32lua en tu PATH).
asmcheck Compila las pruebas y ensambla cada resultado (.vbin), comprobando el ensamblador generado.
v32check Como asmcheck, y además empaqueta cada prueba en un cartucho .v32.
demos Compila todas las demos bajo demos/ (ver Compilar las demos).
version Muestra la versión de inc/v32lua.h y la estampa en las páginas de manual y en doc/DEBUGGING.md (ejecútalo antes de una publicación y luego recompila). make version VERSION=AAAAMMDD-estado escribe antes una versión nueva en inc/v32lua.h.
monofiles, context, put Reúnen las fuentes en put/ como archivos de texto únicos, para pegarlos en una conversación.
archive Limpia y luego comprime el proyecto en v32lua-project.zip.

La cadena de versión está en un solo lugar, #define VERSION en inc/v32lua.h; v32lua --version la muestra y make version la copia en la página de manual y en la guía de depuración. La compilación con CMake y el flujo de publicación la leen de esa misma línea, y una compilación de publicación se detiene si la página de manual o la guía de depuración todavía llevan otra versión.

Compilar con CMake

Para quien lo prefiera, CMakeLists.txt compila el mismo compilador a partir de las mismas fuentes (el Makefile sigue siendo la compilación principal, y make tests sigue siendo la ejecución completa de las pruebas en v32sim):

cmake -S . -B build              # Release por defecto
cmake --build build              # build/v32lua
ctest --test-dir build           # compila cada programa de prueba
sudo cmake --install build       # ver "Instalación" más abajo

ctest comprueba que cada programa de las categorías de testing/ que ejecuta make tests compila, y que los de testing/fail/ se rechazan; ejecutarlos es tarea de make tests. Se usan flex y bison si se encuentran (-DV32LUA_REGENERATE_PARSER=OFF los omite); si no, se compilan tal cual src/parser.c, inc/parser.h y src/lexer.c, ya generados, así que una compilación normal solo necesita un compilador de C y CMake 3.13+. En Windows, compila con MinGW-w64 de 64 bits (por ejemplo desde una terminal MINGW64 de MSYS2, cmake -S . -B build -G "MSYS Makefiles"); MSVC no está soportado. cpack --config build/CPackConfig.cmake genera un .tar.gz (más .deb / .rpm donde haya dpkg-deb / rpmbuild), o un .zip en Windows.

Instalación

Una instalación en el sistema coloca todo donde una instalación de Vircon32 lo espera. El binario y la página de manual van donde van los comandos y las páginas de manual; la biblioteca de --#include y los documentos van en v32tools/, la carpeta que comparten las herramientas de la comunidad (con v32opt y v32c++), junto a las DevTools/ oficiales:

  Linux / macOS Windows (CMake)
compilador /usr/local/bin/v32lua C:\Program Files\Vircon32\v32tools\v32lua.exe
página de manual /usr/local/share/man/man1/v32lua.1.gz (comprimida con gzip) ...\v32tools\docs\v32lua\v32lua.1
biblioteca de --#include (lib/*.lua) /usr/local/Vircon32/v32tools/include/v32lua ...\v32tools\include\v32lua
documentación /usr/local/Vircon32/v32tools/docs/v32lua ...\v32tools\docs\v32lua

Cualquiera de las dos compilaciones lo hace:

sudo make sysinstall                          # Linux / macOS
sudo make sysuninstall

sudo cmake --install build                    # cualquier sistema (prefijo: /usr/local,
sudo cmake --build build --target uninstall   #   o C:/Program Files/Vircon32)

El directorio de la biblioteca queda integrado en el compilador, así que tras instalar, --#include "string.lua" funciona desde cualquier directorio. Con CMake, elige otra ubicación al configurar (cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/vircon32) y el compilador buscará allí; -DV32LUA_INSTALL_INCLUDEDIR=... (y BINDIR, MANDIR, DOCDIR) mueven una sola parte. La desinstalación elimina solo los archivos de v32lua, y después v32tools/ (y en Linux / macOS Vircon32/) solo si no queda nada más dentro. En Windows, añade C:\Program Files\Vircon32\v32tools a tu PATH, como para las DevTools. El Makefile acepta PREFIX= (sudo make sysinstall PREFIX=/opt/vircon32, y DESTDIR= para preparar un paquete); el compilador encuentra la biblioteca instalada así junto a sí mismo (ver más abajo), sin recompilar.

Idioma de la documentación. Por defecto, ambas compilaciones instalan los documentos en todos los idiomas (README.md, README.es.md, README.fr.md, doc/API.es.md, …) y la página de manual en inglés (man/v32lua.es.1 y man/v32lua.fr.1 son las traducidas). DEST_LANG=es o DEST_LANG=fr (sudo make sysinstall DEST_LANG=es, o cmake -S . -B build -DDEST_LANG=es) instala en cambio ese idioma: el README, doc/API.md y doc/USAGE.md traducidos, con esos nombres simples (y los enlaces entre ellos ajustados), junto a las guías que solo existen en inglés (doc/PICO8.md, doc/TIC80.md, …), y la página de manual en ese idioma (man/v32lua.es.1, man/v32lua.fr.1). DEST_LANG=en instala solo los documentos en inglés.

Zips de publicación. Los zips precompilados de la página de Releases (Windows x86_64 y arm64, macOS, Linux, Raspberry Pi, y OpenBSD y Haiku cuando esas compilaciones salen bien) siguen la disposición de la instalación en Windows: v32lua (v32lua.exe) junto a include/v32lua/ (la biblioteca de --#include) y docs/v32lua/ (los README, doc/ y la página de manual, en todos los idiomas). En Windows, descomprime uno en C:\Program Files\Vircon32\v32tools; en cualquier otro sitio, mantén juntos el binario y su carpeta include/. El compilador busca los archivos de --#include en include/v32lua junto a sí mismo cuando no los encuentra en ningún otro sitio, así que un zip descomprimido encuentra su biblioteca esté donde esté.

make install, en cambio, copia solo el binario a ~/bin. Los valores por defecto de la compilación con el Makefile (el directorio de la biblioteca, las frecuencias de muestreo y los puertos de mando por defecto, …) están en inc/config.h; cambia uno ahí y recompila, o sobrescríbelo al compilar:

make CFLAGS="-Wall -Wextra -g -I ../inc -DYYDEBUG=1 -DV32LUA_INCLUDE_PATH='\"/opt/v32tools/include/v32lua\"'"

Compilar las demos

Cada demo bajo demos/pico8/ y demos/tic80/ tiene un Makefile que compila, ensambla y empaqueta el cartucho en bin/<demo>.v32. Si el optimizador de ensamblador v32opt está en tu PATH, la misma ejecución también produce un cartucho optimizado, bin/<demo>Opt.v32 (a partir de obj/<demo>Opt.asm y obj/<demo>Opt.xml, el XML gemelo que v32lua escribe junto a cada XML). Los pasos optimizados pueden fallar: si v32opt o el ensamblador rechazan el código optimizado, make muestra el error y continúa, y el cartucho normal se genera de todos modos. Sin v32opt, la compilación optimizada se omite con un aviso; desactívala a la fuerza con make HAVE_OPTIMIZER=, o cambia las opciones con make OPTIMIZER="v32opt -O2".

Tu Primer Cartucho

--#title "v32lua Tech Demo"
--#version "1.0"
--#texture tex_logo "logo.png"

x_pos = 160.0
y_pos = 120.0
speed = 2.5

function init()
    -- Establece el color de fondo usando mapeos de puertos GPU de costo cero
    ioports.gpu.bgcolor = 0xFF003366
    ioports.gpu.texture = tex_logo -- establece la textura
    ioports.gpu.region  = 0 -- establece la región

    -- define la región
    ioports.gpu.minX    = 0
    ioports.gpu.minY    = 0
    ioports.gpu.maxX    = 100
    ioports.gpu.maxY    = 50
    ioports.gpu.hotX    = 0
    ioports.gpu.hotY    = 0
end

function game_loop()
    ioports.gpu.clear()             -- borra la pantalla con bgcolor

    -- Actualiza el estado usando matemática de punto flotante pura
    if ioports.inp.left > 0 then    -- > 0: cuadros que lleva pulsado; < 0: liberado
        x_pos = x_pos - speed
    elseif ioports.inp.right > 0 then
        x_pos = x_pos + speed
    end

    -- Dibujo directo por hardware
    ioports.gpu.x = x_pos
    ioports.gpu.y = y_pos
    ioports.gpu.draw()

    -- Concatenación de cadenas integrada; print() recibe x, y primero
    local frame = system.frames
    if frame > 1000 then
        print(10, 10, "Demo Running: Frame " .. frame)
    end
end

Compílalo con:

$ v32lua -o program.asm program.lua

v32lua emite program.asm y program.xml a su lado (y programOpt.xml, el mismo cartucho apuntando a programOpt.vbin, para una compilación a través del optimizador v32opt); entrega esos archivos al ensamblador de Vircon32 y a packrom para producir un cartucho ejecutable. Consulta El XML del cartucho para saber dónde espera packrom los recursos. También escribe los scripts de compilación tradicionales de Vircon32, Make.sh y Make.bat (consulta Scripts de compilación), salvo que se indique lo contrario con --no-build-scripts.

Uso desde la Línea de Comandos

$ v32lua [options] file

Opciones disponibles:

Opciones del cartucho — cada una anula la pista --# correspondiente del código fuente, así que un cartucho de PICO-8 o TIC-80 compila sin editarlo (--opt=valor también funciona):

Los valores por defecto de estas opciones (frecuencias de muestreo por API, paneles laterales activados/desactivados y un archivo de arte de panel por defecto, círculos rápidos, advertencias) se definen en inc/config.h: cámbialos allí y recompila, o pásalos al compilar (make CFLAGS="... -DV32LUA_DEFAULT_PICO8_RATE=11025"). Precedencia: la opción de línea de comandos, luego la pista --# del código fuente, luego config.h.

$ v32lua celeste.p8 --title "celeste" --rate 11025     # API detectada a partir del .p8
$ v32lua game.tic                                        # cartucho binario de TIC-80

Archivos de entrada: .lua, .p8 (cartucho de PICO-8), .tic (cartucho de TIC-80, solo cartuchos en Lua; se leen el código y los tiles, sprites, mapa, banderas, paleta, formas de onda y SFX del banco 0 — los mismos datos que lleva una exportación .lua de TIC-80).


Capas de Compatibilidad de API

v32lua admite tres superficies de API distintas, seleccionadas con la pista de cartucho --#api (la API nativa de Vircon32 es la opción por defecto cuando no hay ninguna pista --#api presente):

--#api "tic80"   -- opta por la superficie de API compatible con TIC-80
--#api "pico8"   -- opta por la superficie de API compatible con PICO-8

Solo una superficie de API está activa por cartucho; seleccionar tic80 o pico8 reemplaza la superficie de llamadas nativa en lugar de añadirse a ella.

Teclado y ratón (v32io)

⚠ La entrada de teclado y ratón necesita hardware adicional o un emulador modificado. La consola Vircon32 no tiene teclado ni ratón: solo cuatro mandos. Las funciones de teclado y ratón de v32lua (key()/keyp()/kbd.* y mouse()/mouse.* nativas, key()/keyp()/ mouse() de TIC-80, stat(32..34) de PICO-8) leen un teclado o un ratón disfrazado de mando, y funcionan solo con una de estas opciones:

  • el adaptador de hardware v32io — una placa Waveshare RP2350-USB-A (sin la resistencia R13) con el firmware v32io: un teclado o ratón USB conectado a ella aparece en el ordenador como un mando (v32io:kbd / v32io:mouse), así que funciona con cualquier emulador de Vircon32, incluido el original, una vez configurado su perfil de joystick;
  • un emulador de Vircon32 modificado, que lee el teclado y el ratón del propio ordenador como dispositivos v32io (sin hardware): el fork wedge1020/ComputerSoftware, rama v32io (con v32kbd y v32mouse), o las fuentes del DesktopEmulator original con los parches emulator/v32io/v32kbd.patch, emulator/v32io/v32kbd-text.patch y emulator/v32io/v32mouse.patch del proyecto v32io aplicados.

Sin una de ellas, el acceso al teclado y al ratón no es posible: los programas compilan y se ejecutan, pero ninguna tecla está pulsada nunca y el ratón no se mueve. Los programas que los necesiten deberían indicarlo y, si es posible, ofrecer también controles con mando.

El teclado se lee por defecto del puerto de mando 2 (el tercero) y el ratón del puerto 3 (el cuarto), lo que deja los puertos 0 y 1 para mandos normales (--keyboard N, --mouse N, --#keyboard N, --#mouse N, o kbd.port(n) / mouse.port(n) en tiempo de ejecución). Los puertos se numeran desde 0; los menús del emulador llaman al puerto 2 “Gamepad 3” y al puerto 3 “Gamepad 4”. Los protocolos, la lectura en cada cuadro que añade el compilador y la configuración se describen en doc/API.es.md.


Pistas de Recursos del Cartucho

v32lua te permite incrustar metadatos de cartucho de Vircon32 directamente en tu código fuente Lua usando comentarios de línea especiales --#. El compilador analiza estas pistas para autogenerar la definición ROM .xml del proyecto y asignar IDs de recursos de hardware secuenciales.

Pistas soportadas:

| Pista | Propósito | | — | — | | --#version "X.Y" | Establece el campo de versión del cartucho en el XML. | | --#title "TÍTULO" | Establece el título del cartucho. | | --#titleopt "TÍTULO" | Establece el título del cartucho optimizado (gameOpt.xml); por defecto, el título del cartucho seguido de ` [optimized]. | | –#no-diff-opt-title | El cartucho optimizado conserva el mismo título que el normal (un –#titleopt explícito sigue prevaleciendo). | | –#api “tic80” / –#api “pico8” | Selecciona una capa de compatibilidad de API (ver arriba). | | –#p8 “cart.p8” | PICO-8: toma la hoja de sprites, las banderas de sprites y el mapa de un cartucho .p8 (implica –#api pico8). | | –#rate 11025 \| 22050 \| 44100 | Frecuencia de muestreo del sonido sintetizado a partir de un cartucho de PICO-8 o TIC-80 (por defecto 22050; –#p8rate es el nombre antiguo; ver [doc/PICO8.md](/v32lua/doc/PICO8.html#sound)). | | –#bezel off \| on \| “arte.png” | PICO-8: los paneles laterales junto a la pantalla de 128×128 — ninguno, el arte integrado o el tuyo propio (ver [doc/PICO8.md](/v32lua/doc/PICO8.html#side-panels)). | | –#fast-circles | TIC-80/PICO-8: los círculos rellenos de radio mayor que 31 se dibujan como un único disco escalado (más rápido, los bordes difieren ligeramente). | | –#allow-float-as-clearcolor | Igual que la opción de la línea de órdenes. | | –#keyboard 0–3 | Puerto de mando de un teclado v32kbd (requiere un adaptador v32io o el emulador modificado, ver [Teclado y ratón](#teclado-y-ratón-v32io)), para key()/keyp()/kbd. (2 por defecto; ver [doc/API.es.md](/v32lua/doc/API.es.html#teclado-key--keyp--kbd)). | | –#mouse 0–3 | Puerto de mando de un ratón v32mouse (requiere un adaptador v32io o el emulador modificado, ver [Teclado y ratón](#teclado-y-ratón-v32io)), para mouse()/mouse. y stat(32..34) de PICO-8 (3 por defecto; ver [doc/API.es.md](/v32lua/doc/API.es.html#ratón-mouse--mouse)). | | –#texture NOMBRE “ruta/imagen.png” | Registra un recurso de textura y lo vincula a una constante NOMBRE en tiempo de compilación. El XML nombra el archivo .vtex (imagen.vtex) que png2vircon genera a partir del PNG (consulta [El XML del cartucho](#el-xml-del-cartucho)). | | –#sound NOMBRE “ruta/sonido.wav” | Registra un recurso de sonido y lo vincula a una constante NOMBRE en tiempo de compilación. El XML nombra el archivo .vsnd que genera wav2vircon. | | –#tilemap NOMBRE “ruta/mapa.csv” | Registra un mapa de mosaicos desde un archivo CSV, incrustado directamente en la imagen ROM (ver [doc/API.es.md](/v32lua/doc/API.es.html#mapa-de-mosaicos-tilemap)). | | –#include “archivo.lua”` | Empalma textualmente otro archivo Lua en este punto, antes de que comience el análisis (ver abajo). |

--#version "1.1"
--#title "Space Grinder: Tech Demo"

-- Registra texturas (vincula automáticamente 'bg_space' al ID 0, 'spr_ship' al ID 1)
--#texture bg_space "assets/background.png"
--#texture spr_ship "assets/player.png"

function init()
    -- ¡Las variables declaradas en las pistas están disponibles globalmente en Lua en tiempo de ejecución!
    ioports.gpu.texture = bg_space
end

Al compilar, v32lua genera tanto el ensamblador .asm compilado como un archivo completo de definición de cartucho XML de Vircon32 que enlaza los recursos .vtex y .vsnd. Con esto, y el procesamiento adecuado de cualquier dato PNG y WAV, puedes proceder al paso de packrom. Los IDs de recursos se asignan en el orden del código fuente y se garantiza que coincidan con su posición en el XML generado.

El XML del cartucho

packrom lee todas las rutas del XML relativas al propio archivo XML, así que por defecto v32lua escribe el XML junto al archivo de -o y coloca en él solo nombres de archivo: con -o obj/game.asm y --#texture SHIP "textures/ship.png" obtienes obj/game.xml con

<binary path="game.vbin" />
    <texture path="ship.vtex" /> <!-- SHIP -->

y se esperan los archivos .vbin, .vtex y .vsnd a su lado, en obj/. Las texturas y sonidos que v32lua genera por sí mismo (cartuchos PICO-8 y TIC-80) ya se escriben allí. Si dos recursos se reducen al mismo nombre, el posterior conserva solo los directorios necesarios para ser único: textures/ship.png seguido de textures/extra/ship.png da ship.vtex y extra/ship.vtex (el segundo se espera en obj/extra/); solo dos recursos cuyas rutas completas se reducen a un mismo nombre (que difieren solo en la extensión, por ejemplo) provocan un aviso.

--xml-preserve-paths restaura las rutas tal como se escribieron (textures/ship.vtex), escribe el XML en la carpeta del proyecto: el directorio del archivo fuente, o su directorio padre cuando el fuente está en src/ y la salida en una carpeta hermana como obj/ (con -o obj/game.asm src/game.lua resulta game.xml junto a src/ y obj/), y hace que <binary> sea la ruta del .vbin relativa a él (obj/game.vbin). Las rutas de las pistas pasan a ser relativas al XML (src/textures/ship.vtex) y los recursos generados por el compilador también se nombran relativos al directorio del XML.

Junto a cada XML se escribe un gemelo para la compilación optimizada (obj/gameOpt.xml, idéntico salvo por <binary path="gameOpt.vbin" /> y su título, <título> [optimized] a menos que fijes --#titleopt / --titleopt o pidas el mismo título con --no-diff-opt-title), listo para la salida de v32opt; no hace falta ningún paso con sed.

Scripts de compilación (Make.sh / Make.bat)

A partir de la misma información que el XML, v32lua escribe los scripts de compilación tradicionales de Vircon32: Make.sh para Linux/macOS y Make.bat para Windows. Cada uno crea obj/ y bin/, vuelve a ejecutar v32lua (con tus opciones), convierte los archivos de --#texture / --#sound con png2vircon / wav2vircon, ensambla y empaqueta bin/game.v32, deteniéndose con BUILD FAILED en el primer error y terminando con BUILD SUCCESSFUL. Primero comprueban que v32lua esté en el PATH (y avisan de que hace falta si no), y construyen el cartucho optimizado (v32opt, assemble, packrom de gameOpt.xml a bin/gameOpt.v32) solo si v32opt está instalado.

Se escriben junto al archivo fuente, o en su directorio padre cuando el fuente está en src/ y la salida en una carpeta hermana como obj/. Un Make.sh / Make.bat que no empiece con la marca Generated by v32lua nunca se sobrescribe. --no-build-scripts los desactiva. Consulta doc/USAGE.es.md.

Compilaciones de BIOS (-b)

Con -b, v32lua construye una BIOS de Vircon32 (el programa de arranque de la consola) en lugar de un cartucho, con las reglas de la propia cadena de herramientas de Vircon32: el programa define una function error_handler() sin parámetros, que la consola ejecuta cuando la CPU produce un error de hardware, además de main() (o game_loop()); declara exactamente una --#texture y como máximo un --#sound; y usa la API nativa de vircon32 (no pico8 ni tic80). Todas las infracciones encontradas se informan a la vez. El ensamblador generado comienza entonces con el vector de errores de hardware (CALL __function_error_handler, WAIT, HLT) antes del inicio normal, y sus direcciones de ROM caen en la página de la BIOS (0x10000000) en lugar de la del cartucho (0x20000000). El <rom> del XML recibe type="bios" y los scripts de compilación pasan -b a v32lua y a assemble.

Proyectos Multi-Archivo (--#include)

Debido a que los cartuchos de Vircon32 son una ROM fija ensamblada por completo en tiempo de compilación — no existe un sistema de archivos en tiempo de ejecución — v32lua no admite el require/dofile dinámico de Lua real. En su lugar, --#include "archivo.lua" es un pegado textual en tiempo de compilación, resuelto por un paso de preprocesamiento antes de que el lexer vea siquiera el archivo, exactamente igual que el #include de C:

--#include "src/physics.lua"
--#include "src/entities.lua"

Proceso de Compilación

Flujo de Compilación

  1. Análisis Léxico y Sintáctico: Flex/Bison analiza el código fuente Lua en un Árbol de Sintaxis Abstracta (AST) tipado.
  2. Resolución de Símbolos y Ámbitos: Resuelve variables a través de los ámbitos léxicos, mapeando las globales a direcciones RAM secuenciales y las locales a posiciones de pila [BP - offset].
  3. Generación de Código: Emite instrucciones de ensamblador de Vircon32, aplicando sustituciones de intrínsecos de hardware mientras recorre el AST.
  4. Ensamblado del Cartucho: Emite el archivo .asm final, incrusta las rutinas de soporte en tiempo de ejecución, genera la sección de datos de cadenas de solo lectura, y produce la definición de cartucho .xml.

Etapas de Compilación (-v)

Cuando -v está habilitado, v32lua reporta su progreso a través de sus etapas de pipeline:

  1. Etapa 1: Lexer — Tokeniza el código fuente Lua, eliminando los comentarios estándar y procesando las secuencias de escape de cadenas (\n, \t, \r, \\, \").
  2. Etapa 2: Preprocesador — Expande las directivas --#include y evalúa las pistas de cartucho (--#...) y las sintaxis de comentarios personalizadas.
  3. Etapa 3: Parser — Construye un Árbol de Sintaxis Abstracta (AST) completo usando una gramática LALR(1) de Bison con una precedencia de operadores estricta (núcleo PEMDAS + lógico).
  4. Etapa 4: Analizador Semántico — Ejecuta una pre-pasada para registrar los símbolos globales de funciones y variables e inicializar el ámbito global.
  5. Etapa 5: Emisor — Recorre el AST para generar ensamblador de Vircon32, aplicando la asignación de registros y los desplazamientos de ámbito, y finalmente produce el archivo de configuración XML del cartucho.

Características Clave del Lenguaje y del Compilador

Modelos de Ejecución Flexibles: main() vs. game_loop()

Para acomodar diferentes estilos de arquitectura de juego, el compilador admite dos puntos de entrada de función distintos:

Además, como paso previo a cualquiera de los anteriores:

Un programa debe declarar al menos una de main() o game_loop() — este es el punto de entrada designado, y su ausencia es un error de compilación.

NaN-Boxing: Elementos en RAM vs. ROM

v32lua usa una arquitectura de etiquetado de 32 bits que empaqueta metadatos de tipo y punteros de carga útil en valores unificados, manteniendo los elementos de ROM inmutables (literales de cadena, punteros de función) separados de los objetos de montículo (heap) en RAM dinámicos (tablas):

Tipo de Dato Máscara/Etiqueta Hex Descripción de la Arquitectura
Nil 0xFFC00000 Representación canónica para valores indefinidos/ausentes.
Booleano Falso 0xFFC00001 Valor falso de cortocircuito.
Booleano Verdadero 0xFFC00002 Valor verdadero de cortocircuito.
Cadena ROM 0x7FC00000 Punteros a secciones de datos de cadena de solo lectura (__string_%d) en ROM.
Cadena RAM 0xFFC00000 Cadenas construidas en tiempo de ejecución, en el montículo (carga útil 4 en adelante).
Función 0x7F800000 Dirección de código en ROM, o (con el bit 21 activo) un registro de clausura en RAM.
Tabla 0xFF800000 Direcciones de memoria del montículo empaquetadas (Bit 31=1, Bit 22=0).
Número Flotante IEEE 754 Valores de punto flotante nativos de Vircon32 sin empaquetar, para matemática directa.

Intrínsecos de Hardware y Mapeo de E/S

Los juegos de alto rendimiento de Vircon32 no pueden permitirse búsquedas en tablas hash para la manipulación de hardware. v32lua intercepta expresiones específicas de acceso a miembros de tabla y llamadas a funciones, y las compila directamente en instrucciones nativas de E/S de hardware:

Consulta doc/API.es.md para la referencia completa y autoritativa — este README resalta las ideas, el documento de la API cubre cada llamada.

Experiencia de Desarrollo y Herramientas de Depuración


Características de Lua Soportadas

v32lua implementa un subconjunto de Lua, adaptado específicamente para el desarrollo de juegos en hardware embebido.

Variables y Ámbito

En la jerga de Lua, las funciones son “ciudadanas de primera clase”, y son efectivamente variables. Eso se confirma en v32lua, ya que ambas se transportan dentro del esquema de NaN-boxing.

Asignación Múltiple

El compilador admite de forma nativa la asignación múltiple y el intercambio de variables sin requerir temporales explícitos del usuario:

local x, y, z = 10, 20, 30
x, y = y, x -- Sintetiza cadenas de registros temporales para intercambiar valores de forma segura

Operadores a nivel de bits

Los &, |, ~ (xor), <<, >> y ~ unario (not) de Lua 5.3/5.4. Todo número es un float32, así que cada operación toma sus operandos como palabras de 32 bits y convierte el resultado de vuelta:

Límite: un float32 tiene 24 bits significativos, así que un resultado como 0xDEADBEEF vuelve redondeado; las máscaras y los campos empaquetados con menos bits significativos (0xFF000000, 0xF0F0) son exactos. Las expresiones de literales (1 << 4) se evalúan en tiempo de compilación.

Programación Orientada a Objetos y Tablas

v32lua proporciona azúcar sintáctico transparente para modelos de POO basados en tablas:

function Player.move(dx, dy) --[[ cuerpo ]] end
-- Se desazucara a: Player["move"] = __function_Player_move
Player:move(5, -2)
-- Se desazucara a: Player.move(Player, 5, -2)

Flujo de Control

Operadores y Expresiones

Funciones y Retornos de Múltiples Valores

Los argumentos se pasan por la pila. Las funciones pueden retornar varios valores: los tres primeros vuelven en los registros R0, R2 y R3, y los demás pasan por un búfer reservado en RAM. Como en Lua, una llamada o ... al final de una lista se expande a todos sus valores — en argumentos (f(a, g())), constructores de tabla ({g()}), return x, ... y local a, b = ... — hasta 32 valores. Las funciones variádicas (function f(...)), las clausuras con upvalues (locales capturadas, compartidas entre las clausuras que las capturan) y la recursión funcionan.

Agrupación de Literales de Cadena

Todos los literales de cadena declarados en el código fuente (por ejemplo, "GAME OVER") se recopilan durante la compilación, se deduplican y se emiten en una sección de datos dedicada al final de la ROM (__string_0: string "GAME OVER"), evitando el consumo redundante de ROM.

Evaluación Truthy / Falsy con Cortocircuito

En Lua, solo nil y false evalúan como falso en expresiones condicionales; cualquier otro valor (incluyendo 0 y las cadenas vacías) es verdadero (truthy). v32lua implementa esto mediante dos primitivas de emisión de ensamblador de alta velocidad:

Cuando se evalúan los operadores lógicos (and, or), el resultado evaluado se deja intacto en el registro de destino, preservando el idioma de Lua de retornar el valor real del operando en lugar de un booleano estricto.


Metatablas

setmetatable, getmetatable, rawget, rawset, rawlen y rawequal. Se admiten estos eventos:

Point = {}
Point.__index = Point
function Point.new(x, y) return setmetatable({x = x, y = y}, Point) end
function Point:len2() return self.x * self.x + self.y * self.y end

La metatabla solo se consulta cuando una lectura no encuentra nada o una escritura añade una clave nueva. Las tablas sin metatabla ejecutan el mismo código que antes. Los eventos aritméticos, de comparación y de concatenación (__add, __eq, __lt, __concat, …) no están soportados: los operadores se compilan a instrucciones de coma flotante directas, y comprobar en cada una si hay una tabla ralentizaría toda la aritmética.

E/S de Hardware e Intrínsecos del Compilador

Una de las características más poderosas de v32lua es su motor de interceptación de intrínsecos estático. Cuando el compilador encuentra accesos a tablas o llamadas a funciones que coinciden con rutas de sistema específicas (por ejemplo, ioports.gpu.clear()), evita por completo las búsquedas dinámicas en tablas y emite instrucciones de E/S de hardware de Vircon32 directas (IN, OUT).

Conversión Automática de Tipos en los Límites de E/S

Debido a que las variables de Lua se almacenan como flotantes IEEE 754 empaquetados en NaN mientras que los puertos de hardware de Vircon32 esperan enteros de 32 bits o booleanos, v32lua inyecta automáticamente instrucciones de conversión de hardware durante las lecturas y escrituras de puertos:

Tabla de Referencia Completa de Intrínsecos

La referencia completa y autoritativa para cada intrínseco — ioports.gpu.*, ioports.inp.*, ioports.spu.*, ioports.tim.*, ioports.rng.*, ioports.car.*, ioports.mem.*, music.*/sfx.*, tilemap.*, memcard.* y system.* — vive en doc/API.es.md, incluyendo advertencias sobre el orden de puertos, firmas de llamadas y ejemplos trabajados. Su Referencia rápida enumera cada intrínseco y cada puerto ioports.* (incluidos todos los de ioports.gpu.*) en una sola página. Una breve muestra de las entradas de uso más común:

Control y Dibujo GPU (ioports.gpu.*)

Ruta Lua / Intrínseco Puerto/Comando Vircon32 Acceso Descripción y Comportamiento
ioports.gpu.texture GPU_SelectedTexture Lectura / Escritura Establece o lee el ID de textura activo usado para las operaciones de dibujo.
ioports.gpu.region GPU_SelectedRegion Lectura / Escritura Selecciona la sub-región de textura (cuadro de sprite) a renderizar.
ioports.gpu.x / ioports.gpu.y GPU_DrawingPointX/Y Lectura / Escritura Coordenadas de pantalla para la colocación del dibujo.
ioports.gpu.minX/minY/maxX/maxY GPU_RegionMin/MaxX/Y Lectura / Escritura Define los límites en píxeles de la región de textura activa.
ioports.gpu.hotX/hotY GPU_RegionHotSpotX/Y Lectura / Escritura Establece el origen de dibujo (hotspot) relativo a la región del sprite.
ioports.gpu.draw([mode]) GPU_Command Llamada a Función Ejecuta un comando de dibujo de hardware: "zoom", "rotate", "rotozoom", o el valor por defecto.
ioports.gpu.clear([color])
ioports.gpu.clear(r, g, b [, a])
GPU_ClearColor + GPU_Command Llamada a Función Establece el color de borrado y limpia la pantalla. Admite cadenas de color preestablecidas ("black", "white", "blue", "red", "green"), un valor empaquetado 0xAABBGGRR (un literal, o rgba()/hex() para un valor en tiempo de ejecución), o componentes separados: clear(r, g, b [, a]), cada uno 0–255, con alfa opaco por defecto.
rgba(r, g, b [, a]) — Intrínseco La palabra empaquetada 0xAABBGGRR (en bruto, no un número de Lua) para el color_mult de spr(), ioports.gpu.clear(color), ioports.gpu.multiply / bgcolor. Componentes limitados a 0–255 y truncados, alfa 255 por defecto; se evalúa en tiempo de compilación cuando todos son literales. Guarda los colores como componentes y llama a rgba() donde se dibujan — ver doc/API.es.md para saber por qué.
color(n) — Intrínseco Un número que contiene un color empaquetado (calculado, o leído de una tabla), como palabra en bruto. Redondeado hacia abajo y ajustado a 32 bits; los literales son exactos, los valores en tiempo de ejecución conservan los 24 bits significativos de float32 (detalles).
rect(x1, y1, x2, y2 [, color])
rectfill(x1, y1, x2, y2 [, color])
GPU_Command (dibujo escalado) Intrínseco Contorno / rectángulo relleno entre dos esquinas incluidas, en cualquier orden; color es una palabra empaquetada como en spr() (blanco por defecto). Un dibujo de GPU para rectfill, hasta cuatro para rect; el estado de la GPU se restaura después (detalles).

Mando de Juego y Entrada (ioports.inp.*)

Ruta Lua / Intrínseco Puerto/Comando Vircon32 Acceso Descripción y Comportamiento
ioports.inp.gamepad INP_SelectedGamepad Lectura / Escritura Selecciona el índice del controlador activo (0-3) para el sondeo de entrada. Al leerlo devuelve el último índice asignado (los emuladores no pueden releer el puerto).
ioports.inp.status INP_GamepadConnected Solo Lectura Retorna un booleano de Lua: si el mando seleccionado está conectado.
ioports.inp.left/right/up/down INP_Gamepad* Solo Lectura Estado direccional del D-Pad (> 0 presionado, < 0 liberado).
ioports.inp.A/B/X/Y/L/R/START INP_GamepadButton* Solo Lectura Estado de botón de acción/gatillo (> 0 presionado, < 0 liberado).
ioports.inp.inputs Subrutina de Acción Personalizada Solo Lectura Intrínseco de recopilación: sondea todos los botones/ejes del mando en una sola pasada, los combina en una única máscara de bits de 32 bits, y la convierte a un flotante de Lua.
key([k]), keyp([k [, hold, period]]) dispositivo v32kbd en un puerto de mando Intrínseco Solo con un adaptador v32io o el emulador modificado (por qué). Un teclado completo a través de un dispositivo v32kbd (puerto de mando 2 por defecto, --#keyboard N): tecla pulsada / pulsada en este cuadro, al estilo de TIC-80, con códigos de tecla o nombres literales ("a", "enter", "shift") (detalles).
kbd.read(), kbd.event(), kbd.glyph(v), kbd.port([n]), kbd.capslock(), kbd.connected(), kbd.clear() dispositivo v32kbd Intrínseco Texto escrito (con Mayús y Bloq Mayús), eventos de pulsar/soltar, el puerto del teclado, Bloq Mayús, dispositivo presente, descartar eventos no leídos.
mouse() dispositivo v32mouse en un puerto de mando Intrínseco Solo con un adaptador v32io o el emulador modificado (por qué). Un ratón a través de un dispositivo v32mouse (puerto de mando 3 por defecto, --#mouse N): x, y, left, middle, right, scrollx, scrolly, como el de TIC-80 (desplazamiento siempre 0) (detalles).
mouse.pressed/released([b]), mouse.buttons(), mouse.delta(), mouse.position([x, y]), mouse.bounds(...), mouse.scale([n]), mouse.port([n]), mouse.connected() dispositivo v32mouse Intrínseco Cambios de botones en este cuadro, botones pulsados, movimiento en este cuadro, el puntero, sus límites y velocidad, el puerto del ratón, dispositivo presente.

Utilidades del Sistema y de Ejecución

Ruta Lua / Intrínseco Instrucción Vircon32 Acceso Descripción y Comportamiento
system.halt() HLT Llamada a Función Emite la instrucción de hardware HLT, terminando de inmediato la ejecución de la CPU o congelando el cuadro hasta el siguiente ciclo de interrupción/cuadro.
system.wait() WAIT Llamada a Función Emite la instrucción de hardware WAIT, pausando la ejecución hasta el siguiente ciclo de interrupción/cuadro.
system.frames() / system.cycles() TIM_FrameCounter / TIM_CycleCounter Solo Lectura Cuadros desde el encendido / ciclos de CPU usados hasta ahora en este cuadro (el () es opcional).
print(x, y, value) __builtin_tostring + __builtin_print Llamada a Función Convierte value en cadena y lo dibuja en pantalla con la fuente de la BIOS, en la posición en píxeles x, y.

Ensamblador en Línea (__asm__ y __rawasm__)

Para bucles internos críticos en rendimiento o manipulación avanzada del hardware de Vircon32, v32lua proporciona inyección directa de ensamblador en línea.

Ensamblador en Línea Estándar (__asm__)

La directiva __asm__ permite incrustar cadenas de ensamblador crudo de Vircon32 directamente dentro de funciones Lua. Fundamentalmente, admite interpolación de variables, permitiendo un puente fluido entre los símbolos de ámbito de Lua y los registros de ensamblador:

local speed = 5.0
__asm__("MOV R0, {speed}\nFADD R0, 1.5\nMOV {speed}, R0")

Ensamblador en Bruto (__rawasm__)

La directiva __rawasm__ produce la cadena literal directamente en el flujo de ensamblador sin ninguna salvaguarda aplicada. Esto puede ser bastante peligroso, y solo debería usarlo el usuario de ensamblador más conocedor y experimentado. También es la base del propio arnés de pruebas unitarias del compilador: un archivo de prueba es típicamente un envoltorio function main() ... end alrededor de una secuencia de bloques __rawasm__ con etiquetas __debugN: para establecer puntos de interrupción bajo v32sim.


Mapa de Memoria de Referencia

Dirección RAM Designación Uso
0 HEAP_POINTER Almacena la dirección de inicio dinámica para las asignaciones de tablas/cadenas en tiempo de ejecución.
1, 2 FTOA_SCRATCH_PTR_A/B Palabras de trabajo (scratch) reservadas usadas por la rutina de conversión de flotante a cadena.
3 a HEAP_START - 1 RAM Global Ranuras asignadas secuencialmente para variables globales de Lua, IDs de recursos y locals de nivel superior promovidas.
HEAP_START en adelante Montículo (Heap) Dinámico Memoria en tiempo de ejecución gestionada por el asignador de tablas y las rutinas de cadenas.
Tope de la Pila (SP) Pila de Llamadas Registros de activación de funciones, variables locales y estados de registros guardados.

HEAP_START se calcula después de que ha terminado toda la generación de código, de modo que la generación de código de las sentencias de nivel superior que registra globales tardías nunca pueda colisionar con el montículo.

La ROM de programa no forma parte de esta tabla: el código y los datos constantes de un cartucho están en 0x20000000 (V32_CART_PAGE); los de una BIOS construida con -b, en 0x10000000.


Peculiaridades y supuestos del compilador

Aunque v32lua intenta ser un compilador de Lua funcional, de ninguna manera es una implementación completa y conforme a la especificación del lenguaje. Para empezar, no hay máquina virtual de bytecode, ni intérprete — Lua se compila directamente a ensamblador nativo.

Además, existen algunas desviaciones explícitas respecto a una implementación estándar del lenguaje para adaptarse mejor al entorno autónomo (freestanding) de Vircon32:

Claramente, este esfuerzo está enfocado en crear una herramienta para el desarrollo en Vircon32, y no en ser una implementación de Lua totalmente conforme. Se harán esfuerzos por acercarse tanto como sea posible y factible, sin sacrificar un rendimiento significativo ni alejarse de ser la herramienta que pretende ser.

Optimización del Compilador

Al comienzo del desarrollo del compilador, todo el código de optimización fue eliminado y trasladado a una herramienta separada, v32opt. Está diseñada como un optimizador de ensamblador de Vircon32 de propósito general, pensado para usarse con el compilador de C y el compilador de Lua (junto con ensamblador escrito a mano). Las pruebas tempranas han mostrado mejoras leves en el rendimiento, y un ahorro potencial de espacio al eliminar instrucciones redundantes.

Al momento de escribir esto, esta herramienta todavía está muy en desarrollo, pero muestra promesa y probablemente funcionará para escenarios estándar bajo los niveles de optimización -O1, -O2, e incluso -O3. Está pensada para insertarse en la cadena de compilación después de compilar y antes de ensamblar. Los Makefiles de las demos la usan cuando está instalada, generando un bin/<demo>Opt.v32 optimizado junto al cartucho normal (ver Compilar las demos y doc/USAGE.es.md).

Hoja de Ruta / Aún No Implementado

Lo siguiente son brechas conocidas y deliberadas, no errores — ya sea diferidas para mantener avanzando el desarrollo inicial, o pendientes de una decisión de diseño:


Uso de IA

NOTA: Hubo un uso e interacción extensivos de IA a lo largo de este esfuerzo. Debe hacerse una distinción respecto al “vibe coding”, pero sin duda existe una difuminación entre lo humano y la IA. Al final, ambos se benefician y podrían compensar las deficiencias del otro.

Este esfuerzo en realidad no se trataba principalmente de desarrollar un compilador; comenzó como un intento honesto de tener una idea de la IA y su impacto: su papel y su detrimento en el pensamiento y la educación humanos. Que tuviera un tema de compilador fue simplemente para acentuar un punto de interés. Sin duda ha sido una experiencia de aprendizaje. Si no se hubiera conocido suficientemente los conceptos de compiladores y el trasfondo necesario antes de comenzar esto, el esfuerzo habría terminado mucho menos exitosamente.