Les fonctions
Déclarer des FUNCTION avec paramètres par défaut, optionnels, variadiques et REF, rendre des valeurs typées, surcharger selon le nombre d'arguments, s'accrocher au cycle de vie de l'Engine et passer des poignées.
En un mot#
Une FUNCTION est un bloc de code nommé qui reçoit des paramètres et rend une
valeur. Elle s'exécute du début à la fin d'un seul tenant, dans l'image en
cours. Cette page explique comment la déclarer, comment les valeurs entrent et
sortent, quels noms l'Engine appelle de lui-même, et les poignées qui permettent
à une valeur de désigner une variable.
Déclarer une fonction#
FUNCTION <name>[<sigil>]([<parameter>, ...]) [AS <type>[[]...]]
<statements>
ENDFUNCTION [<value>]
FUNCTION Area(width, height)
ENDFUNCTION width * height
FUNCTION ShowTitle()
Console("Niveau 1")
ENDFUNCTION
ShowTitle() ' Niveau 1
Console(Str(Area(3, 4))) ' 12
ENDFUNCTIONs'écrit en un mot.END FUNCTIONest refusé avec « FUNCTION requires ENDFUNCTION ».- Un appel porte toujours ses parenthèses, même sans argument :
ShowTitle(). - L'ordre n'a pas d'importance. On peut appeler une fonction plus haut que
la ligne qui la déclare, et la déclarer après le
DO … LOOPprincipal. - Les noms sont insensibles à la casse.
area(3, 4)appelleArea. - Déclarez les fonctions au premier niveau d'un fichier, jamais dans un
IF, une boucle ou une autre fonction. - Une fonction utilisateur l'emporte sur une commande du catalogue de même
nom. Nommez une fonction
Stret chaqueStr(...)du programme appelle la vôtre.
⚠️ Un nom seul n'est pas un appel. ShowTitle seul sur une ligne compile,
et n'échoue qu'à l'exécution, parce que le compilateur le lit comme une
variable. Écrivez ShowTitle().
Les graphies PureBasic sont des alias : Procedure vaut FUNCTION,
ProcedureReturn vaut EXITFUNCTION, EndProcedure vaut ENDFUNCTION. La
forme typée Procedure.s Name() n'est pas acceptée : écrivez
Procedure Name() AS string.
Procedure Greet(name AS string) AS string
ProcedureReturn "Bonjour " + name
EndProcedure
Console(Greet("Ada")) ' Bonjour Ada
| Dialecte | Ce qui change dans aukimi |
|---|---|
| AGK, DarkBASIC | Mêmes FUNCTION / ENDFUNCTION valeur / EXITFUNCTION valeur. aukimi ajoute les valeurs par défaut, OPTIONAL, les paramètres variadiques et les surcharges. |
| PureBasic | Procedure marche comme alias, mais les types s'écrivent AS <type>, pas .s ou .i après le mot-clé. |
| Visual Basic | Pas de Sub : une FUNCTION qui ne rend rien fait office de procédure. REF se place après le nom du paramètre (voir plus bas). |
Rendre une valeur#
Il y a trois façons de quitter une fonction :
| Vous écrivez | Où | Ce que cela fait |
|---|---|---|
ENDFUNCTION <valeur> | la dernière ligne | rend la valeur quand le code arrive au bout |
EXITFUNCTION [<valeur>] | n'importe où dans le corps | sort tout de suite, avec ou sans valeur |
RETURN [<valeur>] | n'importe où dans le corps | la même chose qu'EXITFUNCTION dans une fonction |
FUNCTION Grade$(score)
IF score >= 90 THEN EXITFUNCTION "A"
IF score >= 50
RETURN "B"
ENDIF
ENDFUNCTION "C"
Console(Grade$(95) + Grade$(60) + Grade$(10)) ' ABC
EXITFUNCTIONmarche sur unIF … THENd'une ligne, comme ci-dessus.- Une fonction qui se termine sans valeur ne rend rien. Si vous affichez ce
résultat, vous voyez
undefined. - Une fonction qui a un type de retour (
AS integer,AS string…) rend à la place la valeur par défaut de ce type :0,"", un enregistrement vide.
⚠️ EXIT FUNCTION en deux mots n'est pas refusé, mais le programme ne peut
pas tourner. Écrivez EXITFUNCTION.
FUNCTION Sign(n)
IF n < 0 THEN EXIT FUNCTION -1
ENDFUNCTION 1Les retours typés#
AS <type> après les parenthèses convertit chaque valeur rendue dans ce type.
FUNCTION Whole(n) AS integer
ENDFUNCTION n
FUNCTION Label(n) AS string
ENDFUNCTION n
Console(Str(Whole(7.9))) ' 7
Console(Label(3) + Label(4)) ' 34
- Un type d'enregistrement (
AS Vec) ou un type tableau (AS integer[], jusqu'à six[]) rend une copie indépendante. Modifier ce que vous avez reçu ne modifie jamais l'original. - Un type inconnu est une erreur de compilation : « Unknown BASIC return type ».
TYPE Vec
x AS float
y AS float
ENDTYPE
FUNCTION MakeVec(x AS float, y AS float) AS Vec
LOCAL v AS Vec
v.x = x
v.y = y
ENDFUNCTION v
p AS Vec
p = MakeVec(3, 4)
Console(Str(p.x + p.y)) ' 7Un sigil sur le nom d'une fonction#
Un sigil ($, #, %, &) peut terminer le nom d'une fonction, comme en
BASIC classique. C'est une indication pour le lecteur, pas une conversion.
FUNCTION Half#(n)
ENDFUNCTION n / 2
FUNCTION Title$(name$)
ENDFUNCTION "Sire " + name$
Console(Str(Half#(7))) ' 3.5
Console(Title("Robin")) ' Sire Robin
- On peut appeler la fonction avec ou sans son sigil :
Title("Robin")etTitle$("Robin")sont le même appel. - Le sigil ne convertit jamais la valeur.
Whole%(9.8)sansASrend9.8. Pour convertir, écrivezAS integer. - Le sigil ne fait pas partie du nom.
FUNCTION Score(a)etFUNCTION Score$(a)déclarent la même fonction : écrire les deux est une erreur.
Les paramètres#
[OPTIONAL] <name>[<sigil>] [REF | BYREF] [AS <type>[[]...]] [= <default>]
PARAMARRAY <name> [AS <type>]
<name> [AS <type>]...
Les deuxième et troisième lignes sont les formes variadiques. Elles doivent être le dernier paramètre.
Valeurs par défaut et OPTIONAL#
FUNCTION Box(width, height = width)
ENDFUNCTION width * height
FUNCTION Greet$(name$ = "inconnu", OPTIONAL mark AS string)
ENDFUNCTION "Bonjour " + name$ + mark
Console(Str(Box(3))) ' 9
Console(Str(Box(3, 2))) ' 6
Console(Greet$()) ' Bonjour inconnu
Console(Greet$("Ada", " !")) ' Bonjour Ada !
= <défaut>rend un paramètre optionnel. La valeur par défaut est calculée à chaque appel : elle peut donc utiliser un paramètre précédent (height = width).OPTIONALsans valeur par défaut donne la valeur par défaut du type du paramètre :0pour un nombre,""pourAS string, un enregistrement ou un tableau vide.- Un paramètre obligatoire ne peut pas suivre un paramètre optionnel : erreur de compilation.
⚠️ Un sigil ne type pas un paramètre optionnel. OPTIONAL name$ reçoit 0,
et non "", quand on l'omet. Écrivez OPTIONAL name AS string, ou donnez-lui
une valeur par défaut : name$ = "".
⚠️ On ne peut pas sauter un argument au milieu. F(1, , 5) n'est pas
refusé, mais la case vide disparaît et 5 atterrit dans le deuxième paramètre.
Passez tous les arguments jusqu'au dernier dont vous avez besoin :
F(1, 2, 5).
Les paramètres variadiques : PARAMARRAY et ...#
Le dernier paramètre peut recueillir tous les arguments restants dans un tableau.
FUNCTION Average#(PARAMARRAY values AS float)
LOCAL sum# = 0
IF values.length < 0 THEN EXITFUNCTION 0
FOR i = 0 TO values.length
sum# = sum# + values[i]
NEXT i
ENDFUNCTION sum# / (values.length + 1)
FUNCTION Join$(separator$, parts...)
result$ = ""
FOR i = 0 TO parts.length
IF i > 0 THEN result$ = result$ + separator$
result$ = result$ + parts(i)
NEXT i
ENDFUNCTION result$
Console(Str(Average#(2, 4, 9))) ' 5
Console(Str(Average#())) ' 0
Console(Join$("-", "a", "b", "c")) ' a-b-c
PARAMARRAY valuesetvalues...sont la même chose.- Avec un type, écrivez
PARAMARRAY values AS floatouvalues AS float.... La formevalues... AS floatest refusée. - Chaque valeur est convertie dans le type, et les enregistrements sont copiés.
- Un paramètre variadique ne peut avoir ni valeur par défaut ni
REF.
⚠️ .length est le dernier indice, pas le nombre d'éléments (comme dans
AGK). Trois valeurs donnent values.length = 2, et aucune valeur donne -1.
Bouclez avec FOR i = 0 TO values.length, et comptez avec
values.length + 1.
Les paramètres typés#
AS <type> convertit l'argument quand la fonction démarre.
FUNCTION Pad$(text AS string, width AS integer)
WHILE Len(text) < width
text = "0" + text
ENDWHILE
ENDFUNCTION text
Console(Pad$(42, 5.7)) ' 00042
Ici, 42 est devenu le texte "42", et 5.7 l'entier 5.
- N'importe quel type numérique,
string,HANDLE, unTYPEque vous avez déclaré, ou un tableau de ceux-ci (AS integer[],AS integer[][], jusqu'à six dimensions). - Un type inconnu est une erreur de compilation : « Unknown BASIC parameter type ».
⚠️ AS Sprite n'est pas un type de paramètre. La déclaration de famille qui
active la notation pointée vaut pour une variable, pas pour un paramètre, et
FUNCTION Move(s AS Sprite) est refusé. Dans une fonction, appelez la commande
complète : SpriteX(s), SpritePosition(s, x, y), et non s.x.
Les copies, et REF#
Par défaut, une fonction travaille sur une copie de ce que vous lui passez.
REF passe la variable elle-même : la fonction peut alors la modifier.
TYPE Hero
hp AS integer
ENDTYPE
FUNCTION HealCopy(h AS Hero)
h.hp = 100
ENDFUNCTION
FUNCTION HealRef(h REF AS Hero)
h.hp = 100
ENDFUNCTION
hero AS Hero
hero.hp = 10
HealCopy(hero)
Console(Str(hero.hp)) ' 10
HealRef(hero)
Console(Str(hero.hp)) ' 100
| Paramètre | Un nombre ou une chaîne | Un enregistrement ou un tableau |
|---|---|---|
p (sans type) | copie | ⚠️ partagé : les modifications atteignent l'appelant |
p AS Hero, p AS integer[] | copie, convertie | copie |
p REF AS Hero | la variable de l'appelant | la variable de l'appelant |
⚠️ Un paramètre sans type ne copie ni un enregistrement ni un tableau. Seul un paramètre typé le fait. Si une fonction ne doit pas toucher à l'original, donnez son type au paramètre.
Où se place REF#
<name> REF AS <type>
<name> BYREF AS <type>
REF et BYREF sont le même mot. Les deux se placent après le nom et
avant AS, et AS <type> est obligatoire.
FUNCTION SwapValues(a REF AS integer, b REF AS integer)
LOCAL t AS integer = a
a = b
b = t
ENDFUNCTION
x AS integer = 1
y AS integer = 2
SwapValues(x, y)
Console(Str(x) + " " + Str(y)) ' 2 1
L'ordre de Visual Basic est refusé (« Invalid BASIC parameter »), tout comme
REF sans type :
FUNCTION Bump(BYREF n AS integer)
n = n + 1
ENDFUNCTION
- L'argument doit être un emplacement affectable : une variable, un champ
d'enregistrement, un élément de tableau, l'élément courant d'une liste ou une
entrée de dictionnaire. Un littéral ou une expression est refusé :
SwapValues(1, y)etSwapValues(x + 1, y)donnent « REF argument is not assignable ». - REF se transmet. Une fonction peut passer son propre paramètre
REFà un autre paramètreREF, et la modification remonte jusqu'au premier appelant. - Le type s'applique quand même. Un
REF AS integerqui reçoit une variable flottante y écrit un entier. - Un paramètre
REFne peut être niOPTIONALni variadique.
⚠️ Ne mettez pas de sigil sur un paramètre REF. text$ REF AS string
compile, mais le programme ne tourne pas dès que le corps lit text$. Écrivez
text REF AS string.
Les surcharges selon le nombre d'arguments#
Plusieurs fonctions peuvent partager un nom si elles prennent un nombre d'arguments différent.
FUNCTION Area(side)
ENDFUNCTION side * side
FUNCTION Area(width, height)
ENDFUNCTION width * height
Console(Str(Area(3))) ' 9
Console(Str(Area(3, 4))) ' 12
- Le choix se fait à la compilation, sur le seul nombre d'arguments. Les types ne sélectionnent jamais une surcharge.
- Chaque fonction accepte une plage de nombres : au moins ses paramètres obligatoires, au plus tous ses paramètres, sans limite avec un variadique. Deux plages ne doivent pas se chevaucher.
- Un appel qu'aucune version n'accepte est une erreur de compilation : « No BASIC overload of Area accepts 1 argument(s) ».
FUNCTION Area(side)
ENDFUNCTION side * side
FUNCTION Area(width, OPTIONAL height = 1)
ENDFUNCTION width * height
Celui-ci est refusé avec « Ambiguous BASIC overload: Area » : un appel à un argument conviendrait aux deux.
Les variables dans une fonction#
Les paramètres et les variables LOCAL appartiennent à la fonction. Ils sont
créés à chaque appel et disparaissent au retour. Les variables GLOBAL sont
partagées par tout le programme.
OPTION EXPLICIT
GLOBAL score AS integer = 0
FUNCTION AddPoints(amount AS integer) AS integer
LOCAL bonus AS integer = amount DIV 10
score = score + amount + bonus
ENDFUNCTION score
AddPoints(50)
Console(Str(AddPoints(20))) ' 77
Ce qui se passe quand une fonction affecte un nom qui existe aussi en haut du programme :
| En haut du programme | Dans la fonction |
|---|---|
GLOBAL score (n'importe où, même après la fonction) | la variable partagée |
score AS integer = 0 (typée, sans mot-clé) | la variable partagée |
score = 0 sur une ligne au-dessus de la fonction | la variable partagée |
score = 0 sur une ligne en dessous de la fonction | ⚠️ une nouvelle variable locale |
LOCAL score | ⚠️ une nouvelle variable locale |
La lecture marche dans tous les cas, une fois exécutée la ligne qui crée la variable. Seule l'affectation réserve une surprise.
⚠️ LOCAL au premier niveau met une variable hors de portée des fonctions.
Une fonction qui écrit score = score + 1 reçoit alors son propre score, et
le programme s'arrête sur « Cannot access 'score' before initialization ».
Déclarez les variables partagées avec GLOBAL.
Activez
OPTION EXPLICITet déclarez les variables partagées avecGLOBAL. Un nom non déclaré dans une fonction devient alors une erreur de compilation, au lieu d'une copie locale silencieuse.
La récursivité marche : une fonction peut s'appeler elle-même.
FUNCTION Fact(n AS integer) AS integer
IF n <= 1 THEN EXITFUNCTION 1
ENDFUNCTION n * Fact(n - 1)
Console(Str(Fact(5))) ' 120
Voir Variables et types pour
GLOBAL, LOCAL et OPTION EXPLICIT en détail.
Ce qu'une fonction ne peut pas faire#
Une fonction va jusqu'au bout sans rendre l'image. Rien de ce qui attend n'y est donc permis.
| Dans une FUNCTION | Résultat |
|---|---|
END | erreur de compilation : « END is not supported inside FUNCTION; use EXITFUNCTION » |
Wait(...), Sleep(...) | erreur de compilation |
AWAITTASK, AWAITALL, AWAITANY | erreur de compilation |
Sync(), Swap(), YieldTask() | ⚠️ pas d'erreur de compilation, voir ci-dessous |
⚠️ N'appelez jamais Sync(), Swap() ou YieldTask() dans une fonction.
Dans un programme qui a un DO … LOOP ou un END, le compilateur l'accepte
mais le programme ne peut pas démarrer. Dans un programme qui n'en a pas,
l'appel n'attend rien.
FUNCTION Pause()
Sync()
ENDFUNCTION
DO
Pause()
Sync()
LOOP
Pour attendre d'une image à l'autre, utilisez la boucle principale, un
sous-programme GOSUB (qui, lui, peut attendre) ou une tâche. Voir
Le flux de contrôle et
La boucle de jeu et les tâches.
Les fonctions du cycle de vie#
Un programme peut tourner de deux façons. Soit il a sa propre boucle d'images
(DO … Sync() … LOOP), soit il déclare des fonctions aux noms réservés et
l'Engine les appelle. Après avoir compilé un script, l'Engine cherche ces noms :
| Fonction | Quand l'Engine l'appelle | Arguments |
|---|---|---|
start() | une fois, une fois le script chargé | aucun |
update(dt#) | à chaque image | les secondes écoulées depuis l'image précédente (au plus 0,1) |
fixedUpdate(step#) | à cadence fixe, pour la physique | le pas fixe, 1/60 s |
onCollisionEnter(other), onCollisionStay(other), onCollisionExit(other) | quand un contact physique commence, dure ou cesse | l'autre entité |
onTriggerEnter(other), onTriggerExit(other) | quand une entité entre dans un déclencheur ou en sort | l'autre entité |
onMessage(message$, data, sender) | quand un autre script envoie un message, par exemple avec EntitySend("door", "open") | le message, ses données, l'expéditeur |
onAnimationEvent(name$, data) | quand une animation atteint un événement | le nom de l'événement et ses données |
main() | à la place de start(), si le programme en a une | aucun |
onStart, onUpdate et onFixedUpdate sont acceptés pour les trois premiers.
onCollision et onTrigger valent onCollisionEnter et onTriggerEnter.
onAnimEvent vaut onAnimationEvent.
GLOBAL elapsed# = 0
GLOBAL hits AS integer = 0
FUNCTION start()
Console("prêt")
ENDFUNCTION
FUNCTION update(dt#)
elapsed# = elapsed# + dt#
ENDFUNCTION
FUNCTION onCollisionEnter(other)
hits = hits + 1
Console("touché par " + other.name)
ENDFUNCTION
FUNCTION onMessage(message$, data, sender)
IF message$ = "reset" THEN hits = 0
ENDFUNCTION
- Elles gardent leur nom. En BASIC la casse n'importe pas (
Updatemarche) ; l'Engine reçoit exactement la graphie qu'il attend. - Elles ne peuvent pas être surchargées. Un second
updateest une erreur de compilation : « BASIC lifecycle function cannot be overloaded ». - Les lignes hors de toute fonction s'exécutent une fois, au chargement du
script, avant
start(). C'est la place des déclarationsGLOBALet des préparatifs. - Pas de
Sync()dansupdate. L'Engine appelleupdateune fois par image ; l'image se termine quand la fonction rend la main. - Une erreur dans
updatene l'arrête pas. L'erreur est signalée, etupdateest rappelée à l'image suivante : la même erreur se répète. Unupdatequi prend plus de 200 ms pendant 5 images de suite est désactivé. - Vous pouvez toujours appeler ces fonctions vous-même, comme les autres.
⚠️ Un programme à DO … LOOP tourne comme main(). La boucle, ainsi qu'un
Wait() ou un Sleep() au premier niveau, font construire au compilateur une
main(). L'Engine appelle alors main() et n'appelle jamais start(). Ne
déclarez pas votre propre FUNCTION main() dans un tel programme : la boucle la
remplace sans prévenir.
⚠️ lateUpdate et onDestroy gardent leurs noms réservés, mais l'éditeur ne
les appelle pas sur le script d'une entité à ce jour. Ne comptez pas sur eux
pour la logique du jeu ni pour le nettoyage.
Les poignées#
Une poignée est une valeur qui désigne une variable. On peut la ranger, la passer et la rendre, puis lire ou écrire la variable à travers elle.
@<target>
<name> AS HANDLE
<name> AS POINTER
GetRef(<handle>)
SetRef(<handle>, <value>)
IsRef(<handle>)
ReleaseRef(<handle>)
OPTION EXPLICIT
GLOBAL hp AS integer = 100
GLOBAL link AS HANDLE = @hp
SetRef(link, 75)
Console(Str(hp)) ' 75
Console(Str(GetRef(link))) ' 75
ReleaseRef(link)
IF NOT IsRef(link) THEN Console("libérée") ' libérée
Console(Str(hp)) ' 75
| Vous écrivez | Ce que cela fait |
|---|---|
@cible | fabrique une poignée vers cible |
x AS HANDLE, x AS POINTER | une variable qui contient une poignée (POINTER est le même type). Elle démarre vide. |
GetRef(h) | lit la variable que h désigne |
SetRef(h, valeur) | l'écrit, convertie dans le type de la cible |
IsRef(h) | vrai si h désigne encore quelque chose |
ReleaseRef(h) | met fin à la poignée. La variable elle-même n'est pas touchée. |
Ce que @ accepte : une variable, un champ d'enregistrement (@hero.hp),
un élément de tableau (@scores[1]), l'élément courant d'une liste, une entrée
de dictionnaire (@names("ann")). @42 est une erreur de compilation :
« BASIC @ requires an assignable variable, field, array, list, or map element ».
OPTION EXPLICIT
GLOBAL gold AS integer = 5
FUNCTION AddGold(target AS HANDLE, amount AS integer)
SetRef(target, GetRef(target) + amount)
ENDFUNCTION
FUNCTION MakeCounter() AS HANDLE
LOCAL counter AS integer = 0
ENDFUNCTION @counter
AddGold(@gold, 10)
Console(Str(gold)) ' 15
GLOBAL c AS HANDLE = MakeCounter()
SetRef(c, GetRef(c) + 1)
SetRef(c, GetRef(c) + 1)
Console(Str(GetRef(c))) ' 2
- Une poignée garde sa cible en vie.
MakeCounterrend une poignée vers uneLOCAL, et le compteur survit à la fin de la fonction. - Le type de la cible l'emporte. Une poignée vers une variable
AS uint8rangeSetRef(p, 300)sous la forme44. - Lire un enregistrement par une poignée donne une copie dès qu'on l'affecte, comme toute affectation d'enregistrement.
Quand une poignée cesse de marcher#
| Événement | IsRef(h) | GetRef(h) / SetRef(h, …) |
|---|---|---|
ReleaseRef(h) | faux | erreur d'exécution : « BASIC reference handle has been released » |
DELETE de l'enregistrement qui porte le champ | faux | erreur d'exécution |
une variable HANDLE jamais affectée | faux | erreur d'exécution |
Testez IsRef(h) avant d'utiliser une poignée dont vous n'êtes pas sûr. Une
poignée n'est jamais laissée pointée sur de la mémoire libérée.
Poignées, REF et pointeurs classiques#
Paramètre REF | Poignée | |
|---|---|---|
| Durée de vie | le temps d'un appel | tant que vous la gardez |
| Peut être rangée, rendue, mise dans un enregistrement | non | oui |
| Syntaxe | p REF AS T dans la liste des paramètres | @cible, puis GetRef / SetRef |
Le @variable de PureBasic donne une adresse mémoire, et PeekI / PokeI
lisent et écrivent à travers elle. Une poignée aukimi n'est pas un nombre :
pas d'arithmétique d'adresse, pas de conversion en entier, et aucun moyen de la
faire pointer sur une mémoire que vous n'avez pas nommée. C'est ce qui la rend
sûre sur toutes les cibles.