v32lua : Compilateur Lua pour Vircon32

A Vircon32-assembly targeting lua compiler

v32lua : Compilateur Lua pour Vircon32

Version anglaise / in English Version espagnole / en español

Architecture Cible : Console de fantaisie Vircon32 (32-bit)

Langage d’Implémentation : C (Flex/Bison + Émetteur Sémantique Personnalisé)

Dépôt : github.com/wedge1020/v32lua

Référence de l’API : doc/API.fr.md — l’API native complète de Vircon32 (son, graphismes, entrées, tuiles/tilemaps, carte mémoire, et ports d’E/S bruts). Commencez par sa Référence rapide : chaque intrinsèque et chaque port ioports.* réunis en un seul endroit.

Construire une cartouche : doc/USAGE.fr.md — la ligne de commande, les indices --#, le XML généré, et les outils Vircon32 qui terminent le travail.

v32lua est un compilateur Lua écrit en C qui cible la console de fantaisie Vircon32. Plutôt que d’embarquer un interpréteur de bytecode lourd, v32lua analyse le code source Lua et le compile directement en assembleur natif Vircon32, tout en produisant également la définition XML de cartouche dont la chaîne d’outils de la console a besoin pour empaqueter une ROM.

Bien qu’il ne soit pas encore complet, l’un des objectifs du développement est de faire de v32lua un substitut (en aucun cas un remplacement) du Compilateur C de Vircon32 au sein de la chaîne de développement Vircon32. En somme, choisissez votre langage — C ou Lua — et une fois compilé en assembleur, vous poursuivez la construction indépendamment du langage d’implémentation. Des efforts ont donc été faits pour imiter divers comportements du Compilateur C de Vircon32 afin de rendre la substitution de compilateur plus transparente.

Conçu dès le départ en tenant compte des contraintes d’une console de fantaisie rétro, v32lua propose des intrinsèques « matériels » de bas niveau à coût nul, un NaN-boxing personnalisé, et — au-delà de l’API native de Vircon32 — deux couches de compatibilité d’API afin que les cartouches écrites pour TIC-80 et PICO-8 puissent être compilées et exécutées dans l’environnement Vircon32 avec peu ou pas de modification du code source.

+------------------+     +-------------------+     +------------------+
| Source (.lua)    | --> | Lexer & Parseur   | --> | Construction AST |
+------------------+     | (Flex / Bison)    |     +------------------+
                         +-------------------+              |
                                                            v
+------------------+     +-------------------+     +------------------+
| Config Cartouche | <-- | Émetteur Vircon32 | <-- | Émetteur         |
| (.xml)           |     | assembleur (.asm) |     | Sémantique       |
+------------------+     +-------------------+     +------------------+

Table des Matières


Pour Commencer

Prérequis

Compiler le Compilateur

Le dépôt inclut un Makefile à la racine qui gère la compilation du binaire du compilateur, l’exécution de la suite de tests, et l’entretien général du projet. Pour compiler le binaire principal du compilateur depuis les sources, exécutez la cible par défaut depuis la racine du dépôt :

make

Ceci compile les différents fichiers sources et produit le binaire v32lua (sous bin/), qui transforme un fichier source .lua en un fichier .asm Vircon32 accompagné d’un .xml de cartouche. À partir de là, l’assemblage et l’empaquetage suivent les mêmes étapes que n’importe quel autre projet Vircon32 (assembler → packrom → exécuter sous v32sim ou sur l’émulateur officiel).

Tableau de Référence des Cibles du Makefile

Cible Description
all Cible par défaut. Compile le compilateur (bin/v32lua) dans src/.
clean Supprime les artefacts de compilation de src/ et les fichiers générés dans testing/ et demos/.
install Copie bin/v32lua dans ~/bin/bin.$(ARCH)/ si ce répertoire existe, sinon dans ~/bin/.
sysinstall Installation système : bin/v32lua dans /usr/local/bin/, la page de manuel dans /usr/local/share/man/man1/, la bibliothèque de --#include (lib/*.lua) dans /usr/local/Vircon32/v32tools/include/v32lua/ et les documents dans /usr/local/Vircon32/v32tools/docs/v32lua/ (nécessite généralement sudo ; DEST_LANG= choisit la langue des documents et PREFIX= l’emplacement : voir Installation).
sysuninstall Supprime ce que sysinstall a installé.
tests Exécute les tests unitaires de testing/ : chaque programme est compilé, assemblé, empaqueté et exécuté sur v32sim, et ses résultats sont comparés au bloc EXPECTED OUTPUT du fichier (nécessite les DevTools, v32sim et v32lua dans votre PATH).
asmcheck Compile les tests et assemble chaque résultat (.vbin), ce qui vérifie l’assembleur généré.
v32check Comme asmcheck, et empaquette en plus chaque test dans une cartouche .v32.
demos Compile toutes les démos sous demos/ (voir Compiler les démos).
version Affiche la version tirée de inc/v32lua.h et l’inscrit dans les pages de manuel et doc/DEBUGGING.md (à lancer avant une publication, puis recompiler). make version VERSION=AAAAMMJJ-statut écrit d’abord une nouvelle version dans inc/v32lua.h.
monofiles, context, put Rassemblent les sources dans put/ sous forme de fichiers texte uniques, à coller dans une conversation.
archive Nettoie, puis compresse le projet dans v32lua-project.zip.

La chaîne de version se trouve à un seul endroit, #define VERSION dans inc/v32lua.h ; v32lua --version l’affiche et make version la copie dans la page de manuel et le guide de débogage. La compilation CMake et le workflow de publication la lisent sur cette même ligne, et une compilation de publication s’arrête si la page de manuel ou le guide de débogage porte encore une autre version.

Compiler avec CMake

Pour ceux qui le préfèrent, CMakeLists.txt compile le même compilateur à partir des mêmes sources (le Makefile reste la compilation principale, et make tests reste l’exécution complète des tests sur v32sim) :

cmake -S . -B build              # Release par défaut
cmake --build build              # build/v32lua
ctest --test-dir build           # compile chaque programme de test
sudo cmake --install build       # voir « Installation » plus bas

ctest vérifie que chaque programme des catégories de testing/ qu’exécute make tests se compile, et que ceux de testing/fail/ sont refusés ; les exécuter reste le rôle de make tests. flex et bison sont utilisés s’ils sont trouvés (-DV32LUA_REGENERATE_PARSER=OFF les ignore) ; sinon src/parser.c, inc/parser.h et src/lexer.c, déjà générés, sont compilés tels quels, si bien qu’une compilation ordinaire ne demande qu’un compilateur C et CMake 3.13+. Sous Windows, compilez avec MinGW-w64 64 bits (par exemple depuis un terminal MINGW64 de MSYS2, cmake -S . -B build -G "MSYS Makefiles") ; MSVC n’est pas pris en charge. cpack --config build/CPackConfig.cmake produit un .tar.gz (plus .deb / .rpm là où dpkg-deb / rpmbuild sont disponibles), ou un .zip sous Windows.

Installation

Une installation système met tout là où une installation de Vircon32 l’attend. Le binaire et la page de manuel vont là où vont les commandes et les pages de manuel ; la bibliothèque de --#include et les documents vont dans v32tools/, le dossier que partagent les outils de la communauté (avec v32opt et v32c++), à côté des DevTools/ officiels :

  Linux / macOS Windows (CMake)
compilateur /usr/local/bin/v32lua C:\Program Files\Vircon32\v32tools\v32lua.exe
page de manuel /usr/local/share/man/man1/v32lua.1.gz (compressée avec gzip) ...\v32tools\docs\v32lua\v32lua.1
bibliothèque de --#include (lib/*.lua) /usr/local/Vircon32/v32tools/include/v32lua ...\v32tools\include\v32lua
documentation /usr/local/Vircon32/v32tools/docs/v32lua ...\v32tools\docs\v32lua

L’une ou l’autre compilation s’en charge :

sudo make sysinstall                          # Linux / macOS
sudo make sysuninstall

sudo cmake --install build                    # tout système (préfixe : /usr/local,
sudo cmake --build build --target uninstall   #   ou C:/Program Files/Vircon32)

Le répertoire de la bibliothèque est intégré au compilateur : une fois l’installation faite, --#include "string.lua" fonctionne depuis n’importe quel répertoire. Avec CMake, choisissez un autre emplacement à la configuration (cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/opt/vircon32) et le compilateur y cherchera ; -DV32LUA_INSTALL_INCLUDEDIR=... (et BINDIR, MANDIR, DOCDIR) déplacent une seule partie. La désinstallation ne supprime que les fichiers de v32lua, puis v32tools/ (et sous Linux / macOS Vircon32/) seulement s’il n’y reste rien d’autre. Sous Windows, ajoutez C:\Program Files\Vircon32\v32tools à votre PATH, comme pour les DevTools. Le Makefile accepte PREFIX= (sudo make sysinstall PREFIX=/opt/vircon32, et DESTDIR= pour préparer un paquet) ; le compilateur trouve la bibliothèque ainsi installée à côté de lui-même (voir plus bas), sans recompilation.

Langue de la documentation. Par défaut, les deux compilations installent les documents dans toutes les langues (README.md, README.es.md, README.fr.md, doc/API.es.md, …) et la page de manuel en anglais (man/v32lua.es.1 et man/v32lua.fr.1 sont les traduites). DEST_LANG=es ou DEST_LANG=fr (sudo make sysinstall DEST_LANG=fr, ou cmake -S . -B build -DDEST_LANG=fr) installe plutôt cette langue : le README, doc/API.md et doc/USAGE.md traduits, sous ces noms simples (avec les liens entre eux ajustés), à côté des guides qui n’existent qu’en anglais (doc/PICO8.md, doc/TIC80.md, …), et la page de manuel dans cette langue (man/v32lua.es.1, man/v32lua.fr.1). DEST_LANG=en n’installe que les documents en anglais.

Zips de publication. Les zips précompilés de la page Releases (Windows x86_64 et arm64, macOS, Linux, Raspberry Pi, ainsi qu’OpenBSD et Haiku quand ces compilations réussissent) reprennent la disposition de l’installation Windows : v32lua (v32lua.exe) à côté de include/v32lua/ (la bibliothèque de --#include) et de docs/v32lua/ (les README, doc/ et la page de manuel, dans toutes les langues). Sous Windows, décompressez-en un dans C:\Program Files\Vircon32\v32tools ; ailleurs, gardez le binaire et son dossier include/ ensemble. Le compilateur cherche les fichiers de --#include dans include/v32lua à côté de lui-même quand il ne les trouve nulle part ailleurs : un zip décompressé trouve donc sa bibliothèque où qu’il soit.

make install, lui, ne copie que le binaire dans ~/bin. Les valeurs par défaut de la compilation par le Makefile (le répertoire de la bibliothèque, les fréquences d’échantillonnage et les ports de manette par défaut, …) se trouvent dans inc/config.h ; modifiez-en une puis recompilez, ou remplacez-la à la compilation :

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

Compiler les démos

Chaque démo sous demos/pico8/ et demos/tic80/ possède un Makefile qui compile, assemble et empaquette la cartouche dans bin/<demo>.v32. Si l’optimiseur d’assembleur v32opt est dans votre PATH, la même exécution produit aussi une cartouche optimisée, bin/<demo>Opt.v32 (à partir de obj/<demo>Opt.asm et obj/<demo>Opt.xml, le XML jumeau que v32lua écrit à côté de chaque XML). Les étapes optimisées ont le droit d’échouer : si v32opt ou l’assembleur rejette le code optimisé, make affiche l’erreur et continue, et la cartouche normale est tout de même produite. Sans v32opt, la compilation optimisée est sautée avec un message ; désactivez-la de force avec make HAVE_OPTIMIZER=, ou changez les options avec make OPTIMIZER="v32opt -O2".

Votre Première Cartouche

--#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()
    -- Définit la couleur de fond via les mappages de ports GPU à coût nul
    ioports.gpu.bgcolor = 0xFF003366
    ioports.gpu.texture = tex_logo -- définit la texture
    ioports.gpu.region  = 0 -- définit la région

    -- définit la région
    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()             -- efface l'écran avec bgcolor

    -- Met à jour l'état en utilisant des calculs en virgule flottante purs
    if ioports.inp.left > 0 then    -- > 0 : maintenu (en images) ; < 0 : relâché
        x_pos = x_pos - speed
    elseif ioports.inp.right > 0 then
        x_pos = x_pos + speed
    end

    -- Dessin matériel direct
    ioports.gpu.x = x_pos
    ioports.gpu.y = y_pos
    ioports.gpu.draw()

    -- Concaténation de chaînes intégrée ; print() prend d'abord x, y
    local frame = system.frames
    if frame > 1000 then
        print(10, 10, "Demo Running: Frame " .. frame)
    end
end

Compilez-le avec :

$ v32lua -o program.asm program.lua

v32lua produit program.asm et program.xml à ses côtés (ainsi que programOpt.xml, la même cartouche pointant vers programOpt.vbin, pour une compilation passant par l’optimiseur v32opt) ; transmettez-les à l’assembleur Vircon32 puis à packrom pour obtenir une cartouche exécutable. Voir Le XML de la cartouche pour savoir où packrom attend les ressources. Il écrit aussi les scripts de compilation traditionnels de Vircon32, Make.sh et Make.bat (voir Scripts de compilation), sauf avec --no-build-scripts.

Utilisation en Ligne de Commande

$ v32lua [options] fichier

Options disponibles :

Options de cartouche — chacune remplace l’indice --# correspondant dans le source, de sorte qu’une cartouche PICO-8 ou TIC-80 se compile sans modification (--opt=valeur fonctionne aussi) :

Les valeurs par défaut de ces options (fréquences d’échantillonnage par API, panneaux latéraux activés ou non et fichier d’image de panneau par défaut, cercles rapides, avertissements) sont définies dans inc/config.h : modifiez-les là et recompilez, ou passez-les à la compilation (make CFLAGS="... -DV32LUA_DEFAULT_PICO8_RATE=11025"). Priorité : l’option de ligne de commande, puis l’indice --# dans le source, puis config.h.

$ v32lua celeste.p8 --title "celeste" --rate 11025     # API détectée depuis .p8
$ v32lua game.tic                                        # cartouche binaire TIC-80

Fichiers d’entrée : .lua, .p8 (cartouche PICO-8), .tic (cartouche TIC-80, cartouches Lua uniquement ; le code ainsi que les tuiles, sprites, carte, drapeaux, palette, formes d’onde et SFX de la banque 0 sont lus — les mêmes données que contient un export .lua de TIC-80).


Couches de Compatibilité d’API

v32lua prend en charge trois surfaces d’API distinctes, sélectionnées avec l’indice de cartouche --#api (l’API native de Vircon32 est celle par défaut lorsqu’aucun indice --#api n’est présent) :

--#api "tic80"   -- opte pour la surface d'API compatible TIC-80
--#api "pico8"   -- opte pour la surface d'API compatible PICO-8

Une seule surface d’API est active par cartouche ; sélectionner tic80 ou pico8 remplace la surface d’appel native au lieu de s’y ajouter.

Clavier et souris (v32io)

⚠ Le clavier et la souris demandent du matériel en plus ou un émulateur modifié. La console Vircon32 n’a ni clavier ni souris : seulement quatre manettes. Les fonctions clavier et souris de v32lua (key()/keyp()/kbd.* et mouse()/mouse.* natives, key()/keyp()/mouse() de TIC-80, stat(32..34) de PICO-8) lisent un clavier ou une souris déguisés en manette, et ne fonctionnent qu’avec l’une de ces solutions :

  • l’adaptateur matériel v32io — une carte Waveshare RP2350-USB-A (résistance R13 retirée) avec le firmware v32io : un clavier ou une souris USB branchés dessus apparaissent sur l’ordinateur comme une manette (v32io:kbd / v32io:mouse), donc il fonctionne avec n’importe quel émulateur Vircon32, y compris celui d’origine, une fois son profil de joystick configuré ;
  • un émulateur Vircon32 modifié, qui lit le clavier et la souris de l’ordinateur comme des périphériques v32io (sans matériel) : le fork wedge1020/ComputerSoftware, branche v32io (avec v32kbd et v32mouse), ou les sources du DesktopEmulator d’origine auxquelles on applique les correctifs emulator/v32io/v32kbd.patch, emulator/v32io/v32kbd-text.patch et emulator/v32io/v32mouse.patch du projet v32io.

Sans l’une d’elles, l’accès au clavier et à la souris est impossible : les programmes se compilent et s’exécutent, mais aucune touche n’est jamais enfoncée et la souris ne bouge pas. Les programmes qui en ont besoin devraient le dire et, si possible, proposer aussi des commandes à la manette.

Le clavier est lu par défaut sur le port de manette 2 (le troisième) et la souris sur le port 3 (le quatrième), ce qui laisse les ports 0 et 1 aux manettes ordinaires (--keyboard N, --mouse N, --#keyboard N, --#mouse N, ou kbd.port(n) / mouse.port(n) à l’exécution). Les ports sont numérotés à partir de 0 ; les menus de l’émulateur appellent le port 2 « Gamepad 3 » et le port 3 « Gamepad 4 ». Les protocoles, la lecture à chaque image ajoutée par le compilateur et la configuration sont décrits dans doc/API.fr.md.


Indices de Ressources de Cartouche

v32lua vous permet d’intégrer des métadonnées de cartouche Vircon32 directement dans votre code source Lua à l’aide de commentaires de ligne spéciaux --#. Le compilateur analyse ces indices pour générer automatiquement la définition ROM .xml du projet et attribuer des identifiants de ressources matérielles séquentiels.

Indices pris en charge :

| Indice | Objectif | | — | — | | --#version "X.Y" | Définit le champ de version de la cartouche dans le XML. | | --#title "TITRE" | Définit le titre de la cartouche. | | --#titleopt "TITRE" | Définit le titre de la cartouche optimisée (gameOpt.xml) ; par défaut, le titre de la cartouche suivi de ` [optimized]. | | –#no-diff-opt-title | La cartouche optimisée garde le même titre que la normale (un –#titleopt explicite l'emporte toujours). | | –#api “tic80” / –#api “pico8” | Sélectionne une couche de compatibilité d'API (voir ci-dessus). | | –#p8 “cart.p8” | PICO-8 : prend la planche de sprites, les drapeaux de sprites et la carte d'une cartouche .p8 (implique –#api pico8). | | –#rate 11025 \| 22050 \| 44100 | Fréquence d'échantillonnage du son synthétisé à partir d'une cartouche PICO-8 ou TIC-80 (22050 par défaut ; –#p8rate est l'ancien nom ; voir [doc/PICO8.md](/v32lua/doc/PICO8.html#sound)). | | –#bezel off \| on \| “art.png” | PICO-8 : les panneaux latéraux à côté de l'écran 128×128 — aucun, les images intégrées, ou les vôtres (voir [doc/PICO8.md](/v32lua/doc/PICO8.html#side-panels)). | | –#fast-circles | TIC-80/PICO-8 : les cercles pleins de rayon supérieur à 31 sont dessinés comme un seul disque mis à l'échelle (plus rapide, bords légèrement différents). | | –#allow-float-as-clearcolor | Comme l'option de la ligne de commande. | | –#keyboard 0–3 | Port de manette d'un clavier v32kbd (adaptateur v32io ou émulateur modifié nécessaire, voir [Clavier et souris](#clavier-et-souris-v32io)), pour key()/keyp()/kbd. (2 par défaut ; voir [doc/API.fr.md](/v32lua/doc/API.fr.html#clavier--key--keyp--kbd)). | | –#mouse 0–3 | Port de manette d'une souris v32mouse (adaptateur v32io ou émulateur modifié nécessaire, voir [Clavier et souris](#clavier-et-souris-v32io)), pour mouse()/mouse. et stat(32..34) de PICO-8 (3 par défaut ; voir [doc/API.fr.md](/v32lua/doc/API.fr.html#souris--mouse--mouse)). | | –#texture NOM “chemin/image.png” | Enregistre une ressource de texture et la lie à une constante NOM à la compilation. Le XML nomme le fichier .vtex (image.vtex) que png2vircon produit à partir du PNG (voir [Le XML de la cartouche](#le-xml-de-la-cartouche)). | | –#sound NOM “chemin/son.wav” | Enregistre une ressource sonore et la lie à une constante NOM à la compilation. Le XML nomme le fichier .vsnd que produit wav2vircon. | | –#tilemap NOM “chemin/carte.csv” | Enregistre une tilemap depuis un fichier CSV, intégrée directement dans l'image ROM (voir [doc/API.fr.md](/v32lua/doc/API.fr.html#tilemap--tilemap)). | | –#include “fichier.lua”` | Insère textuellement un autre fichier Lua à cet endroit, avant le début de l’analyse (voir ci-dessous). |

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

-- Enregistre des textures (lie automatiquement 'bg_space' à l'ID 0, 'spr_ship' à l'ID 1)
--#texture bg_space "assets/background.png"
--#texture spr_ship "assets/player.png"

function init()
    -- Les variables déclarées dans les indices sont disponibles globalement en Lua à l'exécution !
    ioports.gpu.texture = bg_space
end

Une fois compilé, v32lua produit à la fois l’assembleur .asm compilé et un fichier complet de définition de cartouche XML Vircon32 reliant les ressources .vtex et .vsnd. Avec cela, et le traitement approprié de toute donnée PNG et WAV, vous pouvez procéder à l’étape packrom. Les identifiants de ressources sont attribués dans l’ordre du code source et sont garantis de correspondre à leur position dans le XML généré.

Le XML de la cartouche

packrom lit tous les chemins du XML relativement au fichier XML lui-même ; par défaut, v32lua écrit donc le XML à côté du fichier de -o et n’y place que des noms de fichiers : avec -o obj/game.asm et --#texture SHIP "textures/ship.png", vous obtenez obj/game.xml contenant

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

et les fichiers .vbin, .vtex et .vsnd sont attendus à côté, dans obj/. Les textures et sons que v32lua génère lui-même (cartouches PICO-8 et TIC-80) y sont déjà écrits. Si deux ressources se réduisent au même nom, la suivante ne garde que les répertoires nécessaires pour être unique : textures/ship.png suivi de textures/extra/ship.png donne ship.vtex et extra/ship.vtex (le second attendu dans obj/extra/) ; seules deux ressources dont les chemins complets se réduisent au même nom (ne différant que par l’extension, par exemple) provoquent un avertissement.

--xml-preserve-paths rétablit les chemins tels qu’écrits (textures/ship.vtex), écrit le XML dans le dossier du projet : le répertoire du fichier source, ou son parent quand la source est dans src/ et la sortie dans un dossier voisin comme obj/ (avec -o obj/game.asm src/game.lua, on obtient game.xml à côté de src/ et de obj/), et fait de <binary> le chemin du .vbin relatif à celui-ci (obj/game.vbin). Les chemins des indications deviennent relatifs au XML (src/textures/ship.vtex) et les ressources générées par le compilateur sont elles aussi nommées par rapport au répertoire du XML.

À côté de chaque XML, un jumeau pour la compilation optimisée est écrit (obj/gameOpt.xml, identique sauf <binary path="gameOpt.vbin" /> et son titre, <titre> [optimized] à moins de définir --#titleopt / --titleopt ou de demander le même titre avec --no-diff-opt-title), prêt pour la sortie de v32opt ; aucune étape sed n’est nécessaire.

Scripts de compilation (Make.sh / Make.bat)

À partir des mêmes informations que le XML, v32lua écrit les scripts de compilation traditionnels de Vircon32 : Make.sh pour Linux/macOS et Make.bat pour Windows. Chacun crée obj/ et bin/, relance v32lua (avec vos options), convertit les fichiers de --#texture / --#sound avec png2vircon / wav2vircon, assemble et empaquette bin/game.v32, en s’arrêtant avec BUILD FAILED à la première erreur et en terminant par BUILD SUCCESSFUL. Ils vérifient d’abord que v32lua est dans le PATH (et signalent qu’il est nécessaire sinon), et ne construisent la cartouche optimisée (v32opt, assemble, packrom de gameOpt.xml vers bin/gameOpt.v32) que si v32opt est installé.

Ils sont écrits à côté du fichier source, ou dans son parent quand la source est dans src/ et la sortie dans un dossier voisin comme obj/. Un Make.sh / Make.bat qui ne commence pas par la marque Generated by v32lua n’est jamais écrasé. --no-build-scripts les désactive. Voir doc/USAGE.fr.md.

Compilations de BIOS (-b)

Avec -b, v32lua construit une BIOS Vircon32 (le programme de démarrage de la console) au lieu d’une cartouche, selon les règles de la chaîne d’outils Vircon32 elle-même : le programme définit une function error_handler() sans paramètre, que la console exécute quand le processeur lève une erreur matérielle, en plus de main() (ou game_loop()) ; il déclare exactement une --#texture et au plus un --#sound ; et il utilise l’API native vircon32 (pas pico8 ni tic80). Toutes les infractions trouvées sont signalées en une fois. L’assembleur généré commence alors par le vecteur d’erreur matérielle (CALL __function_error_handler, WAIT, HLT) avant le démarrage normal, et ses adresses de ROM tombent dans la page de la BIOS (0x10000000) au lieu de celle de la cartouche (0x20000000). Le <rom> du XML reçoit type="bios" et les scripts de compilation passent -b à v32lua et à assemble.

Projets Multi-Fichiers (--#include)

Comme les cartouches Vircon32 sont une ROM fixe entièrement assemblée au moment de la compilation — il n’existe aucun système de fichiers à l’exécution — v32lua ne prend pas en charge le require/dofile dynamique du vrai Lua. Au lieu de cela, --#include "fichier.lua" est un collage textuel effectué à la compilation, résolu par une passe de prétraitement avant même que le lexer ne voie le fichier, exactement comme le #include du C :

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

Pipeline de Compilation

Flux de Compilation

  1. Analyse Lexicale et Syntaxique : Flex/Bison analyse le code source Lua en un Arbre de Syntaxe Abstraite (AST) typé.
  2. Résolution des Symboles et des Portées : Résout les variables à travers les portées lexicales, associant les globales à des adresses RAM séquentielles et les locales à des positions de pile [BP - offset].
  3. Génération de Code : Émet des instructions assembleur Vircon32, en appliquant les substitutions d’intrinsèques matériels au fil du parcours de l’AST.
  4. Assemblage de la Cartouche : Émet le fichier .asm final, intègre les routines de support à l’exécution, génère la section de données de chaînes en lecture seule, et produit la définition .xml de la cartouche.

Étapes de Compilation (-v)

Lorsque -v est activé, v32lua rapporte sa progression à travers ses étapes de pipeline :

  1. Étape 1 : Lexer — Décompose le code source Lua en tokens, retire les commentaires standards et traite les séquences d’échappement de chaînes (\n, \t, \r, \\, \").
  2. Étape 2 : Préprocesseur — Développe les directives --#include et évalue les indices de cartouche (--#...) et les syntaxes de commentaires personnalisées.
  3. Étape 3 : Parseur — Construit un Arbre de Syntaxe Abstraite (AST) complet à l’aide d’une grammaire Bison LALR(1) avec une précédence d’opérateurs stricte (noyau PEMDAS + logique).
  4. Étape 4 : Analyseur Sémantique — Exécute une pré-passe pour enregistrer les symboles globaux de fonctions et de variables et initialiser la portée globale.
  5. Étape 5 : Émetteur — Parcourt l’AST pour générer l’assembleur Vircon32, en appliquant l’allocation de registres et les décalages de portée, et produit enfin le fichier de configuration XML de la cartouche.

Fonctionnalités Clés du Langage et du Compilateur

Modèles d’Exécution Flexibles : main() vs. game_loop()

Pour s’adapter à différents styles d’architecture de jeu, le compilateur prend en charge deux points d’entrée de fonction distincts :

De plus, en préalable à l’un ou l’autre des modèles ci-dessus :

Un programme doit déclarer au moins l’une des deux fonctions main() ou game_loop() — il s’agit du point d’entrée désigné, et son absence est une erreur de compilation.

NaN-Boxing : Éléments RAM vs. ROM

v32lua utilise une architecture d’étiquetage sur 32 bits qui compresse les métadonnées de type et les pointeurs de charge utile dans des valeurs unifiées, séparant les éléments ROM immuables (littéraux de chaîne, pointeurs de fonction) des objets de tas (heap) RAM dynamiques (tables) :

Type de Donnée Masque/Étiquette Hex Description de l’Architecture
Nil 0xFFC00000 Représentation canonique des valeurs indéfinies/manquantes.
Booléen Faux 0xFFC00001 Valeur fausse de court-circuit.
Booléen Vrai 0xFFC00002 Valeur vraie de court-circuit.
Chaîne ROM 0x7FC00000 Pointeurs vers des sections de données de chaînes en lecture seule (__string_%d) en ROM.
Chaîne RAM 0xFFC00000 Chaînes construites à l’exécution, sur le tas (charge utile 4 et plus).
Fonction 0x7F800000 Adresse de code en ROM, ou (avec le bit 21 à 1) un enregistrement de fermeture en RAM.
Table 0xFF800000 Adresses mémoire de tas encapsulées (bit 31=1, bit 22=0).
Nombre Flottant IEEE 754 Valeurs en virgule flottante natives de Vircon32, non encapsulées, pour les calculs directs.

Intrinsèques Matériels et Mappage E/S

Les jeux Vircon32 à haute performance ne peuvent pas se permettre des recherches dans des tables de hachage pour la manipulation matérielle. v32lua intercepte des expressions spécifiques d’accès aux membres de table et des appels de fonctions, et les compile directement en instructions d’E/S matérielle natives :

Consultez doc/API.fr.md pour la référence complète et faisant autorité — ce README met en avant les idées, le document d’API couvre chaque appel.

Expérience de Développement et Outillage de Débogage


Fonctionnalités Lua Prises en Charge

v32lua implémente un sous-ensemble de Lua, spécifiquement adapté au développement de jeux sur matériel embarqué.

Variables et Portée

Dans le jargon Lua, les fonctions sont des « citoyennes de première classe », et sont effectivement des variables. Cela se vérifie dans v32lua, puisque les deux transitent au sein du schéma de NaN-boxing.

Affectation Multiple

Le compilateur prend nativement en charge l’affectation multiple et l’échange de variables sans nécessiter de variables temporaires explicites de la part de l’utilisateur :

local x, y, z = 10, 20, 30
x, y = y, x -- Synthétise des chaînes de registres temporaires pour échanger les valeurs en toute sécurité

Opérateurs bit à bit

Les opérateurs &, |, ~ (ou exclusif), <<, >> et ~ unaire (non) de Lua 5.3/5.4. Chaque nombre est un float32, donc chaque opération prend ses opérandes comme des mots de 32 bits et reconvertit le résultat :

Limite : un float32 contient 24 bits significatifs, donc un résultat comme 0xDEADBEEF revient arrondi ; les masques et les champs regroupés ayant moins de bits significatifs (0xFF000000, 0xF0F0) sont exacts. Les expressions de littéraux (1 << 4) sont calculées à la compilation.

Programmation Orientée Objet et Tables

v32lua fournit un sucre syntaxique transparent pour les modèles de POO basés sur des tables :

function Player.move(dx, dy) --[[ corps ]] end
-- Se désucre en : Player["move"] = __function_Player_move
Player:move(5, -2)
-- Se désucre en : Player.move(Player, 5, -2)

Flux de Contrôle

Opérateurs et Expressions

Fonctions et Retours de Valeurs Multiples

Les arguments sont passés sur la pile. Les fonctions peuvent retourner plusieurs valeurs : les trois premières reviennent dans les registres R0, R2 et R3, et les suivantes passent par un tampon réservé en RAM. Comme en Lua, un appel ou ... en fin de liste se développe en toutes ses valeurs — dans les arguments (f(a, g())), les constructeurs de table ({g()}), return x, ... et local a, b = ... — jusqu’à 32 valeurs. Les fonctions variadiques (function f(...)), les fermetures avec upvalues (des locales capturées, partagées entre les fermetures qui les capturent) et la récursivité fonctionnent toutes.

Regroupement des Littéraux de Chaîne

Tous les littéraux de chaîne déclarés dans le code source (par exemple, "GAME OVER") sont collectés lors de la compilation, dédupliqués, et émis dans une section de données dédiée à la fin de la ROM (__string_0: string "GAME OVER"), évitant une consommation redondante de la ROM.

Évaluation Truthy / Falsy en Court-Circuit

En Lua, seuls nil et false s’évaluent comme faux dans les expressions conditionnelles ; toute autre valeur (y compris 0 et les chaînes vides) est vraie (truthy). v32lua implémente cela via deux primitives d’émission d’assembleur à haute vitesse :

Lorsque les opérateurs logiques (and, or) sont évalués, le résultat évalué est laissé intact dans le registre de destination, préservant l’idiome Lua consistant à retourner la valeur réelle de l’opérande plutôt qu’un booléen strict.


Métatables

setmetatable, getmetatable, rawget, rawset, rawlen et rawequal. Ces événements sont pris en charge :

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 métatable n’est consultée que lorsqu’une lecture ne trouve rien ou qu’une écriture ajoute une nouvelle clé. Les tables sans métatable exécutent le même code qu’avant. Les événements arithmétiques, de comparaison et de concaténation (__add, __eq, __lt, __concat, …) ne sont pas pris en charge : les opérateurs sont compilés en instructions flottantes directes, et vérifier à chacune s’il s’agit d’une table ralentirait toute l’arithmétique.

E/S Matérielle et Intrinsèques du Compilateur

L’une des fonctionnalités les plus puissantes de v32lua est son moteur d’interception statique des intrinsèques. Lorsque le compilateur rencontre des accès à des tables ou des appels de fonctions correspondant à des chemins système spécifiques (par exemple, ioports.gpu.clear()), il contourne entièrement les recherches dynamiques dans les tables et émet des instructions d’E/S matérielle Vircon32 directes (IN, OUT).

Conversion Automatique de Types aux Frontières d’E/S

Étant donné que les variables Lua sont stockées sous forme de flottants IEEE 754 encapsulés en NaN, alors que les ports matériels de Vircon32 attendent des entiers 32 bits ou des booléens, v32lua injecte automatiquement des instructions de conversion matérielle lors des lectures et écritures de ports :

Tableau de Référence Complet des Intrinsèques

La référence complète et faisant autorité pour chaque intrinsèque — ioports.gpu.*, ioports.inp.*, ioports.spu.*, ioports.tim.*, ioports.rng.*, ioports.car.*, ioports.mem.*, music.*/sfx.*, tilemap.*, memcard.*, et system.* — se trouve dans doc/API.fr.md, avec les mises en garde sur l’ordre des ports, les signatures d’appel, et des exemples détaillés. Sa Référence rapide liste chaque intrinsèque et chaque port ioports.* (tous les ioports.gpu.* compris) sur une seule page. Voici un bref échantillon des entrées les plus couramment utilisées :

Contrôle et Dessin GPU (ioports.gpu.*)

Chemin Lua / Intrinsèque Port/Commande Vircon32 Accès Description et Comportement
ioports.gpu.texture GPU_SelectedTexture Lecture / Écriture Définit ou lit l’identifiant de texture actif utilisé pour les opérations de dessin.
ioports.gpu.region GPU_SelectedRegion Lecture / Écriture Sélectionne la sous-région de texture (image de sprite) à afficher.
ioports.gpu.x / ioports.gpu.y GPU_DrawingPointX/Y Lecture / Écriture Coordonnées écran pour le placement du dessin.
ioports.gpu.minX/minY/maxX/maxY GPU_RegionMin/MaxX/Y Lecture / Écriture Définit les limites en pixels de la région de texture active.
ioports.gpu.hotX/hotY GPU_RegionHotSpotX/Y Lecture / Écriture Définit l’origine de dessin (hotspot) relative à la région du sprite.
ioports.gpu.draw([mode]) GPU_Command Appel de Fonction Exécute une commande de dessin matérielle : "zoom", "rotate", "rotozoom", ou la valeur par défaut.
ioports.gpu.clear([couleur])
ioports.gpu.clear(r, g, b [, a])
GPU_ClearColor + GPU_Command Appel de Fonction Définit la couleur d’effacement et efface l’écran. Prend en charge des chaînes de couleurs prédéfinies ("black", "white", "blue", "red", "green"), une valeur compressée 0xAABBGGRR (un littéral, ou rgba()/hex() pour une valeur calculée à l’exécution), ou des composantes séparées : clear(r, g, b [, a]), chacune de 0 à 255, alpha opaque par défaut.
rgba(r, g, b [, a]) — Intrinsèque Le mot compressé 0xAABBGGRR (brut, pas un nombre Lua) pour le color_mult de spr(), ioports.gpu.clear(couleur), ioports.gpu.multiply / bgcolor. Composantes limitées à 0–255 et tronquées, alpha à 255 par défaut ; calculé à la compilation quand toutes sont des littéraux. Gardez les couleurs sous forme de composantes et appelez rgba() là où elles sont dessinées — voir doc/API.fr.md pour savoir pourquoi.
color(n) — Intrinsèque Un nombre contenant une couleur compressée (calculée, ou lue dans une table), sous forme de mot brut. Arrondi à l’entier inférieur et ramené sur 32 bits ; les littéraux sont exacts, les valeurs d’exécution gardent les 24 bits significatifs du float32 (détails).
rect(x1, y1, x2, y2 [, color])
rectfill(x1, y1, x2, y2 [, color])
GPU_Command (dessin agrandi) Intrinsèque Contour / rectangle plein entre deux coins inclus, dans n’importe quel ordre ; color est un mot empaqueté comme pour spr() (blanc par défaut). Un dessin GPU pour rectfill, jusqu’à quatre pour rect ; l’état du GPU est rétabli ensuite (détails).

Manette et Entrées (ioports.inp.*)

Chemin Lua / Intrinsèque Port/Commande Vircon32 Accès Description et Comportement
ioports.inp.gamepad INP_SelectedGamepad Lecture / Écriture Sélectionne l’index de la manette active (0-3) pour le sondage des entrées. Sa lecture renvoie le dernier index affecté (les émulateurs ne savent pas relire le port).
ioports.inp.status INP_GamepadConnected Lecture Seule Retourne un booléen Lua : la manette sélectionnée est-elle connectée.
ioports.inp.left/right/up/down INP_Gamepad* Lecture Seule État directionnel de la croix directionnelle (> 0 pressé, < 0 relâché).
ioports.inp.A/B/X/Y/L/R/START INP_GamepadButton* Lecture Seule État des boutons d’action/gâchettes (> 0 pressé, < 0 relâché).
ioports.inp.inputs Sous-routine d’Action Personnalisée Lecture Seule Intrinsèque de regroupement : sonde tous les boutons/axes de la manette en une seule passe, les regroupe en un unique masque de bits sur 32 bits, et le convertit en flottant Lua.
key([k]), keyp([k [, hold, period]]) périphérique v32kbd sur un port de manette Intrinsèque Uniquement avec un adaptateur v32io ou l’émulateur modifié (pourquoi). Un clavier complet à travers un périphérique v32kbd (port de manette 2 par défaut, --#keyboard N) : touche enfoncée / enfoncée à cette image, façon TIC-80, avec des codes de touche ou des noms littéraux ("a", "enter", "shift") (détails).
kbd.read(), kbd.event(), kbd.glyph(v), kbd.port([n]), kbd.capslock(), kbd.connected(), kbd.clear() périphérique v32kbd Intrinsèque Texte tapé (Maj et Verr Maj appliqués), événements d’appui/relâchement, le port du clavier, Verr Maj, périphérique présent, oubli des événements non lus.
mouse() périphérique v32mouse sur un port de manette Intrinsèque Uniquement avec un adaptateur v32io ou l’émulateur modifié (pourquoi). Une souris à travers un périphérique v32mouse (port de manette 3 par défaut, --#mouse N) : x, y, left, middle, right, scrollx, scrolly, comme celui de TIC-80 (défilement toujours 0) (détails).
mouse.pressed/released([b]), mouse.buttons(), mouse.delta(), mouse.position([x, y]), mouse.bounds(...), mouse.scale([n]), mouse.port([n]), mouse.connected() périphérique v32mouse Intrinsèque Changements de boutons à cette image, boutons enfoncés, mouvement à cette image, le pointeur, ses limites et sa vitesse, le port de la souris, périphérique présent.

Utilitaires Système et d’Exécution

Chemin Lua / Intrinsèque Instruction Vircon32 Accès Description et Comportement
system.halt() HLT Appel de Fonction Émet l’instruction matérielle HLT, terminant immédiatement l’exécution du CPU ou figeant l’image jusqu’au prochain cycle d’interruption/image.
system.wait() WAIT Appel de Fonction Émet l’instruction matérielle WAIT, mettant en pause l’exécution jusqu’au prochain cycle d’interruption/image.
system.frames() / system.cycles() TIM_FrameCounter / TIM_CycleCounter Lecture Seule Images écoulées depuis la mise sous tension / cycles CPU utilisés jusqu’ici dans l’image en cours (les () sont facultatives).
print(x, y, valeur) __builtin_tostring + __builtin_print Appel de Fonction Convertit valeur en chaîne et la dessine à l’écran avec la police du BIOS, à la position en pixels x, y.

Assembleur en Ligne (__asm__ et __rawasm__)

Pour les boucles internes critiques en performance ou la manipulation matérielle avancée de Vircon32, v32lua fournit une injection directe d’assembleur en ligne.

Assembleur en Ligne Standard (__asm__)

La directive __asm__ permet d’intégrer des chaînes d’assembleur brut de Vircon32 directement à l’intérieur de fonctions Lua. Fait crucial, elle prend en charge l’interpolation de variables, permettant un pont transparent entre les symboles de portée Lua et les registres d’assembleur :

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

Assembleur Brut (__rawasm__)

La directive __rawasm__ produit la chaîne littérale directement dans le flux d’assembleur sans aucune sécurité appliquée. Cela peut être assez dangereux, et ne devrait être utilisé que par les utilisateurs d’assembleur les plus avertis et expérimentés. C’est également la base du propre harnais de tests unitaires du compilateur : un fichier de test est typiquement un enveloppement function main() ... end autour d’une séquence de blocs __rawasm__ avec des étiquettes __debugN: pour poser des points d’arrêt sous v32sim.


Référence de la Carte Mémoire

Adresse RAM Désignation Utilisation
0 HEAP_POINTER Stocke l’adresse de départ dynamique pour les allocations de tables/chaînes à l’exécution.
1, 2 FTOA_SCRATCH_PTR_A/B Mots de travail (scratch) réservés utilisés par la routine de conversion flottant-vers-chaîne.
3 à HEAP_START - 1 RAM Globale Emplacements alloués séquentiellement pour les variables globales Lua, les identifiants de ressources, et les locals de niveau supérieur promues.
HEAP_START et au-delà Tas (Heap) Dynamique Mémoire d’exécution gérée par l’allocateur de tables et les routines de chaînes.
Sommet de Pile (SP) Pile d’Appels Enregistrements d’activation des fonctions, variables locales, et états de registres sauvegardés.

HEAP_START est calculé après la fin de toute la génération de code, de sorte que la génération de code des instructions de niveau supérieur qui enregistre des globales tardives ne puisse jamais entrer en collision avec le tas.

La ROM de programme ne fait pas partie de ce tableau : le code et les données constantes d’une cartouche sont à 0x20000000 (V32_CART_PAGE), ceux d’une BIOS construite avec -b à 0x10000000.


Particularités et hypothèses du compilateur

Bien que v32lua tente d’être un compilateur Lua fonctionnel, il n’est en aucun cas une implémentation complète et conforme à la spécification du langage. Pour commencer, il n’y a ni machine virtuelle à bytecode, ni interpréteur — Lua est compilé directement en assembleur natif.

De plus, il existe quelques déviations explicites par rapport à une implémentation standard du langage afin de mieux s’adapter à l’environnement autonome (freestanding) de Vircon32 :

Clairement, cet effort est concentré sur la création d’un outil pour le développement sur Vircon32, et non sur une implémentation Lua totalement conforme. Des efforts seront faits pour s’en approcher autant que possible et faisable, sans sacrifier une performance significative ni s’éloigner de l’outil qu’il est censé être.

Optimisation du Compilateur

Au tout début du développement du compilateur, tout le code d’optimisation a été retiré et transféré dans un outil séparé, v32opt. Celui-ci est conçu comme un optimiseur d’assembleur Vircon32 à usage général, destiné à être utilisé avec le compilateur C et le compilateur Lua (ainsi qu’avec de l’assembleur écrit à la main). Les premiers tests ont montré de légères améliorations de performance, et des économies d’espace potentielles en éliminant les instructions redondantes.

Au moment d’écrire ces lignes, cet outil est encore très en cours de développement, mais il montre des promesses et fonctionnera probablement pour des scénarios standards sous les niveaux d’optimisation -O1, -O2, et même -O3. Il est conçu pour être inséré dans la chaîne de compilation après la compilation et avant l’assemblage. Les Makefiles des démos l’utilisent lorsqu’il est installé, en produisant une cartouche optimisée bin/<demo>Opt.v32 à côté de la cartouche normale (voir Compiler les démos et doc/USAGE.fr.md).

Feuille de Route / Pas Encore Implémenté

Ce qui suit sont des lacunes connues et délibérées plutôt que des bugs — soit reportées pour maintenir l’élan du développement initial, soit en attente d’une décision de conception :


Utilisation de l’IA

REMARQUE : Il y a eu une utilisation et une interaction étendues avec l’IA tout au long de cet effort. Une distinction doit être faite par rapport au « vibe coding », mais il existe indéniablement un flou entre l’humain et l’IA. En fin de compte, les deux en bénéficient et pourraient compenser les lacunes de l’autre.

Cette entreprise ne visait en réalité pas principalement à développer un compilateur ; elle a commencé comme une tentative honnête de se faire une idée de l’IA et de son impact : son rôle et son détriment pour la pensée et l’éducation humaines. Le fait qu’elle ait eu un thème de compilateur servait simplement à accentuer un point d’intérêt. Cela a certainement été une expérience d’apprentissage. Si les concepts de compilation et les connaissances de fond nécessaires n’avaient pas été suffisamment connus avant de commencer cela, l’effort se serait terminé de façon bien moins réussie.