Le préprocesseur
Découper un programme en plusieurs fichiers avec #INCLUDE, #INSERT et IncludeFile, et ne compiler des lignes que pour les cibles qui en ont besoin avec #IF et les profils de cible.
En un mot#
Le préprocesseur lit votre programme comme des lignes de texte, avant le compilateur. Il a deux tâches : amener les lignes d'autres fichiers, et garder ou écarter des lignes selon l'endroit où le programme est compilé. Tout ce qui figure sur cette page est réglé avant qu'une seule instruction soit compilée.
Les modules du projet : d'où viennent les fichiers#
Un fichier inclus est un module BASIC du projet, et rien d'autre. Le compilateur ne lit jamais un chemin du disque ni une URL.
- En créer un dans l'éditeur de script avec File › Nouveau script ·
BASIC Module. Il s'appelle
modules/module_1.bas(puismodule_2.bas…) et commence parOPTION EXPLICIT. - En importer un : File › Importer… avec un fichier
.basou.agcl'ajoute comme module, sous le nom du fichier. (Quand l'éditeur est ouvert sur le script d'une entité, Importer remplace ce script à la place.) - Le renommer (clic droit, Renommer) pour le ranger dans un dossier. Le
nom est le chemin : un module renommé
lib/math.bass'inclut avec#INCLUDE "lib/math.bas". - Un module ne s'exécute jamais seul. Il est enregistré avec le projet, et exporté avec lui vers HTML et Bevy.
- Après
#INCLUDE ",#INSERT "ouIncludeFile(", l'éditeur complète les noms de modules.
#INCLUDE, #INSERT et IncludeFile#
#INCLUDE "<path>"
#INSERT "<path>"
IncludeFile("<path>")
Les trois mettent les lignes d'un module à la place de la directive. Ils diffèrent sur un point :
| Directive | Combien de fois |
|---|---|
#INCLUDE | une fois par programme. Les demandes suivantes du même fichier sont ignorées, quel que soit le fichier qui les fait. |
#INSERT | à chaque apparition |
IncludeFile | à chaque apparition (le nom PureBasic de #INSERT) |
Le module lib/math.bas déclare FUNCTION Clamp(value, low, high) :
FUNCTION Clamp(value, low, high)
IF value < low THEN EXITFUNCTION low
IF value > high THEN EXITFUNCTION high
ENDFUNCTION value
Le programme principal peut le demander deux fois. #INCLUDE ne l'amène
qu'une fois :
#INCLUDE "lib/math.bas"
#INCLUDE "lib/math.bas"
Console(Str(Clamp(15, 0, 10))) ' 10
Avec #INSERT, la fonction serait déclarée deux fois, ce qui est refusé :
#INSERT "lib/math.bas"
#INSERT "lib/math.bas"
L'erreur est « Ambiguous BASIC overload: Clamp », signalée dans lib/math.bas.
Utilisez
#INCLUDEpour un fichier de déclarations (fonctions,TYPE,GLOBAL). Utilisez#INSERTpour un morceau de code que vous voulez répéter.
Le module snippets/banner.bas tient en une ligne de code :
Console("==========")
#INSERT "snippets/banner.bas"
Console(" NIVEAU 1")
#INSERT "snippets/banner.bas"
La ligne de bannière s'affiche deux fois, au-dessus et en dessous de
NIVEAU 1.
- Une directive par ligne, seule sur sa ligne. Un commentaire
'en fin de ligne est accepté. - ⚠️ Pas après un
:.x = 1 : #INCLUDE "lib/math.bas"n'est pas refusé, mais la directive reste dans le programme comme du texte et le programme ne peut pas démarrer. - Le chemin s'écrit entre guillemets droits. Les parenthèses sont facultatives
sur les trois :
IncludeFile "lib/math.bas"marche aussi. - Mots-clés et chemins ignorent la casse :
#include "LIB/Math.bas"est la même ligne. - C'est une simple insertion de texte. Une directive placée dans une
FUNCTIONy met les lignes. Une directive placée dans unIFd'exécution est insérée quelle que soit la condition : leIFdécide seulement si ces lignes s'exécutent. Pour écarter un fichier, utilisez#IF, plus bas. #INSERTne compte pas pour#INCLUDE: un fichier inséré plus tôt est quand même amené, une fois, par un#INCLUDEultérieur.
Les chemins#
Un chemin est cherché à deux endroits, dans cet ordre :
- à côté du fichier qui contient la directive (dans son dossier) ;
- depuis la racine du projet.
/ et \ séparent tous deux les dossiers, et .. remonte d'un dossier. Un
chemin qui ne correspond à aucun module est une erreur de compilation, sur la
ligne de la directive : « BASIC include file not found: <chemin> ».
Le module game/player.bas déclare FUNCTION ClampHealth(hp) et a besoin de
lib/math.bas, un dossier plus haut :
#INCLUDE "../lib/math.bas"
FUNCTION ClampHealth(hp)
ENDFUNCTION Clamp(hp, 0, 100)
#INCLUDE "game/player.bas"
#INCLUDE "lib/math.bas"
Console(Str(ClampHealth(140))) ' 100
lib/math.bas est demandé deux fois, par game/player.bas et par le programme
principal, et n'est inclus qu'une fois.
Les cycles#
Deux fichiers qui s'incluent l'un l'autre forment un cycle.
- Avec
#INCLUDE, un fichier déjà inclus est ignoré : le cycle s'arrête de lui-même. Pas d'erreur. - Avec
#INSERTouIncludeFile, un cycle est une erreur de compilation qui nomme la chaîne : « Cyclic BASIC include: loop/a.bas -> loop/b.bas -> loop/a.bas ».
Les modules loop/a.bas et loop/b.bas s'insèrent l'un l'autre :
#INSERT "b.bas"
#INSERT "a.bas"
#INSERT "loop/a.bas"Les erreurs dans un module#
Une erreur dans une ligne incluse est signalée avec le nom du module et son propre numéro de ligne, pas avec une ligne du programme principal. Un double-clic dessus dans la console ouvre ce module. Voir Les erreurs pour le format de la console.
La compilation conditionnelle#
#IF <condition>
<lines>
[#ELSEIF <condition>]
<lines>
[#ELSE]
<lines>
#ENDIF
#IFDEF <symbol>
#IFNDEF <symbol>
#DEFINE <symbol>
#UNDEF <symbol>
CompilerIf <condition>
CompilerElseIf <condition>
CompilerElse
CompilerEndIf
#IF garde les lignes de la première branche dont la condition est vraie, et
écarte les autres. Une condition teste des symboles : des noms définis ou
non, sans valeur.
#IF ENGINE AND DEBUG
Console("version éditeur")
#ELSEIF HTML
Console("export web")
#ELSE
Console("autre cible")
#ENDIF
Dans l'éditeur, cela affiche version éditeur. L'export HTML du même programme
ne contient que la ligne export web.
| Graphie | Sens |
|---|---|
#IF, #ELSEIF (ou #ELIF), #ELSE, #ENDIF | les branches |
#IFDEF X | la même chose que #IF X, pour un seul symbole |
#IFNDEF X | la même chose que #IF NOT X, pour un seul symbole |
CompilerIf, CompilerElseIf, CompilerElse, CompilerEndIf | les noms PureBasic de #IF, #ELSEIF, #ELSE, #ENDIF |
Ce qu'une condition peut contenir#
| Vous écrivez | Vrai quand |
|---|---|
WEB | le symbole WEB est défini |
DEFINED(WEB), DEFINED WEB | la même chose |
NOT WEB, !WEB | WEB n'est pas défini |
WEB AND DEBUG, WEB && DEBUG | les deux le sont |
WEB OR BEVY, WEB || BEVY | au moins l'un l'est |
DEBUG && (WEB OR BEVY) | les parenthèses regroupent : DEBUG, et l'un de WEB ou BEVY |
TRUE, FALSE | toujours, jamais |
NOTlie plus fort queAND, etANDplus fort queOR. Ajoutez des parenthèses en cas de doute.- Un symbole inconnu est simplement faux. Une faute de frappe dans un nom de symbole n'est pas une erreur : vérifiez l'orthographe.
- Rien d'autre n'est permis : ni nombre, ni chaîne, ni comparaison, ni
constante.
#IF 1,#IF LEVEL = 3et leCompilerIf #PB_Compiler_OS = #PB_OS_Windowsde PureBasic sont tous refusés avec « Invalid BASIC compiler condition ». LeDefinedà deux arguments de PureBasic est refusé aussi : écrivezDEFINED(X). - Directives et symboles ignorent la casse :
#if webmarche.
Les règles des blocs#
- Une directive par ligne. Un commentaire
'en fin de ligne est accepté. - Les blocs s'imbriquent à n'importe quelle profondeur.
- Un bloc s'ouvre et se ferme dans le même fichier. Un
#IFdu programme principal ne peut pas se terminer dans un module. - Les lignes d'une branche écartée sont vidées, jamais compilées. Elles peuvent contenir n'importe quoi, même du texte qui n'est pas du BASIC, et les numéros de ligne du reste restent justes.
- Les directives écrites dans un commentaire
REMSTART…REMENDou/*…*/sont ignorées.
| Message | Cause |
|---|---|
#IF requires #ENDIF | un bloc resté ouvert à la fin de son fichier |
#ENDIF without #IF, #ELSE without #IF, #ELSEIF without #IF | aucun bloc ouvert |
#ELSEIF after #ELSE, Duplicate #ELSE | mauvais ordre |
Invalid BASIC compiler condition: … | une condition avec un nombre, une comparaison, ou un #IFDEF à plusieurs symboles |
#DEFINE requires one symbol | un #DEFINE avec une valeur |
#DEFINE et #UNDEF#
#DEFINE ajoute un symbole, #UNDEF en retire un. Servez-vous-en pour vos
propres interrupteurs.
#DEFINE CHEATS
#IF DEFINED(CHEATS) AND NOT RELEASE
Console("touches de triche actives")
#ENDIF
Dans l'éditeur, cela affiche touches de triche actives. Dans un export,
RELEASE est défini et la ligne est écartée.
- Un symbole n'a pas de valeur.
#DEFINE LEVEL 3est refusé, et il n'y a pas de macros. - Il s'applique à partir de sa ligne, pour tout le reste du programme, y
compris les fichiers inclus après lui. Un module rempli de
#DEFINE, inclus en premier, sert de fichier de réglages. - Un
#DEFINEplacé dans une branche écartée ne fait rien. #UNDEFretire aussi un symbole de profil : après#UNDEF DEBUG,#IFDEF DEBUGest faux même dans l'éditeur.
Les profils de cible#
Chaque endroit qui compile un programme part d'un jeu de symboles, son profil :
| Où le programme est compilé | Symboles définis |
|---|---|
| dans l'éditeur : Play, aperçu | ENGINE, WEB, DEBUG |
| l'export HTML | HTML, WEB, RELEASE |
| l'export Bevy | BEVY, RELEASE |
#IF DEBUG
GLOBAL lives AS integer = 99
#ELSE
GLOBAL lives AS integer = 3
#ENDIF
Console(Str(lives)) ' 99 dans l'éditeur, 3 dans un export
DEBUGn'existe que dans l'éditeur, etRELEASEdans chaque export. Ce sont donc les interrupteurs naturels pour les raccourcis de test.- Utilisez
#IF ENGINEpour tenir hors des exports le code qui ne marche que dans l'éditeur. - Le compilateur connaît aussi deux profils Bevy plus précis,
BEVY NATIVE RELEASEetBEVY WASM WEB RELEASE. À ce jour, l'export Bevy compile avec le profil général : testez doncBEVY, pasNATIVEniWASM.
Il passe avant tout le reste#
Le préprocesseur travaille en une passe, de haut en bas :
- les blocs
#IFet les#DEFINE/#UNDEFsont réglés ligne par ligne ; - les lignes
#INCLUDE,#INSERTetIncludeFilegardées sont remplacées par leurs fichiers, qui passent par les deux mêmes étapes ; - alors seulement le compilateur lit le résultat :
@expose,OPTION EXPLICIT, les commandes et tout le reste.
Trois conséquences :
- Un fichier dans une branche écartée n'est jamais lu. Il peut même manquer : pas d'erreur « not found ».
- Une condition ne peut pas tester une variable ni une constante. Elles n'existent pas encore. Seuls les symboles existent.
- Les lignes
@exposesont traitées après cette étape. Voir Variables et types.
#IF BEVY
#INCLUDE "bevy/only.bas"
#ENDIF
Console("compilé") ' compilé
Dans l'éditeur, bevy/only.bas n'a pas besoin d'exister.
Comparé aux dialectes classiques#
| Dialecte | Ce qu'il a | Dans aukimi |
|---|---|---|
| AGK | #include (une fois) et #insert (à chaque fois) | les mêmes mots, avec le même sens. Les fichiers sont des modules du projet. |
| PureBasic | IncludeFile (à chaque fois), XIncludeFile (une fois) | IncludeFile est identique. XIncludeFile est refusé : écrivez #INCLUDE. |
| PureBasic | CompilerIf avec constantes et comparaisons | CompilerIf ne prend que des symboles |
| C, FreeBASIC | #define NOM valeur, macros | #DEFINE définit un symbole sans valeur. Pas de macros. |