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 (puis module_2.bas…) et commence par OPTION EXPLICIT.
  • En importer un : File › Importer… avec un fichier .bas ou .agc l'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.bas s'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 " ou IncludeFile(", 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 :

DirectiveCombien de fois
#INCLUDEune 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 #INCLUDE pour un fichier de déclarations (fonctions, TYPE, GLOBAL). Utilisez #INSERT pour 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 FUNCTION y met les lignes. Une directive placée dans un IF d'exécution est insérée quelle que soit la condition : le IF décide seulement si ces lignes s'exécutent. Pour écarter un fichier, utilisez #IF, plus bas.
  • #INSERT ne compte pas pour #INCLUDE : un fichier inséré plus tôt est quand même amené, une fois, par un #INCLUDE ultérieur.

Les chemins#

Un chemin est cherché à deux endroits, dans cet ordre :

  1. à côté du fichier qui contient la directive (dans son dossier) ;
  2. 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 #INSERT ou IncludeFile, 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.

GraphieSens
#IF, #ELSEIF (ou #ELIF), #ELSE, #ENDIFles branches
#IFDEF Xla même chose que #IF X, pour un seul symbole
#IFNDEF Xla même chose que #IF NOT X, pour un seul symbole
CompilerIf, CompilerElseIf, CompilerElse, CompilerEndIfles noms PureBasic de #IF, #ELSEIF, #ELSE, #ENDIF

Ce qu'une condition peut contenir#

Vous écrivezVrai quand
WEBle symbole WEB est défini
DEFINED(WEB), DEFINED WEBla même chose
NOT WEB, !WEBWEB n'est pas défini
WEB AND DEBUG, WEB && DEBUGles deux le sont
WEB OR BEVY, WEB || BEVYau moins l'un l'est
DEBUG && (WEB OR BEVY)les parenthèses regroupent : DEBUG, et l'un de WEB ou BEVY
TRUE, FALSEtoujours, jamais
  • NOT lie plus fort que AND, et AND plus fort que OR. 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 = 3 et le CompilerIf #PB_Compiler_OS = #PB_OS_Windows de PureBasic sont tous refusés avec « Invalid BASIC compiler condition ». Le Defined à deux arguments de PureBasic est refusé aussi : écrivez DEFINED(X).
  • Directives et symboles ignorent la casse : #if web marche.

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 #IF du 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 … REMEND ou /* … */ sont ignorées.
MessageCause
#IF requires #ENDIFun bloc resté ouvert à la fin de son fichier
#ENDIF without #IF, #ELSE without #IF, #ELSEIF without #IFaucun bloc ouvert
#ELSEIF after #ELSE, Duplicate #ELSEmauvais ordre
Invalid BASIC compiler condition: …une condition avec un nombre, une comparaison, ou un #IFDEF à plusieurs symboles
#DEFINE requires one symbolun #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 3 est 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 #DEFINE placé dans une branche écartée ne fait rien.
  • #UNDEF retire aussi un symbole de profil : après #UNDEF DEBUG, #IFDEF DEBUG est 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çuENGINE, WEB, DEBUG
l'export HTMLHTML, WEB, RELEASE
l'export BevyBEVY, 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
  • DEBUG n'existe que dans l'éditeur, et RELEASE dans chaque export. Ce sont donc les interrupteurs naturels pour les raccourcis de test.
  • Utilisez #IF ENGINE pour 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 RELEASE et BEVY WASM WEB RELEASE. À ce jour, l'export Bevy compile avec le profil général : testez donc BEVY, pas NATIVE ni WASM.

Il passe avant tout le reste#

Le préprocesseur travaille en une passe, de haut en bas :

  1. les blocs #IF et les #DEFINE / #UNDEF sont réglés ligne par ligne ;
  2. les lignes #INCLUDE, #INSERT et IncludeFile gardées sont remplacées par leurs fichiers, qui passent par les deux mêmes étapes ;
  3. 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 @expose sont 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#

DialecteCe qu'il aDans 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.
PureBasicIncludeFile (à chaque fois), XIncludeFile (une fois)IncludeFile est identique. XIncludeFile est refusé : écrivez #INCLUDE.
PureBasicCompilerIf avec constantes et comparaisonsCompilerIf ne prend que des symboles
C, FreeBASIC#define NOM valeur, macros#DEFINE définit un symbole sans valeur. Pas de macros.