Enregistrements et méthodes

Regrouper des champs dans des enregistrements TYPE, les copier comme des valeurs, atteindre leurs champs avec WITH, et leur donner des méthodes, un constructeur et un destructeur.

En un mot#

TYPE déclare un enregistrement : un groupe de champs nommés, chacun avec son type. Un enregistrement est une valeur, comme un nombre : l'affecter à une variable déclarée en fait une copie. METHOD ajoute des fonctions qui appartiennent à l'enregistrement, avec SELF à l'intérieur.

TYPE Ship
  name AS string
  fuel AS integer
  speed AS float
ENDTYPE
hero AS Ship
hero.name = "Comet"
hero.fuel = 80
hero.speed = 2.5
Console(hero.name + " " + Str(hero.fuel) + " " + Str(hero.speed))   ' Comet 80 2.5

TYPE … ENDTYPE#

TYPE <Nom>
  <champ>
  …
  [METHOD …]
ENDTYPE
Forme du champLe champ contient
nameun entier 32 bits
name$une chaîne
name#un nombre décimal
name AS <type>ce type : integer, float, string, uint8… ou un autre type d'enregistrement
name AS <type> [<dernier indice>, …]un tableau de ce type
name [<dernier indice>, …]un tableau d'entiers
LIST name.<type>()une liste, voir Tableaux, listes et dictionnaires
MAP name.<type>([<slots>])un dictionnaire
ARRAY name.<type>(<dernier indice>, …)un tableau, déclaré à la manière de PureBasic
TYPE Player
  name AS string
  score
  lives AS uint8
  slots AS integer [3]
  LIST items.s()
  MAP counts.i()
ENDTYPE
p AS Player
p.name = "Ada"
p.score = 2.9
p.lives = 260
p.slots[3] = 7
AddElement(p.items())
p.items() = "Potion"
p.counts("Potion") = 3
Console(p.name + " " + Str(p.score) + " " + Str(p.lives) + " " + Str(p.slots[3]))   ' Ada 2 4 7
Console(p.items() + " " + Str(p.counts("Potion")))   ' Potion 3

Un champ sans sigil ni AS est un nombre entier. score ci-dessus a rangé 2.9 comme 2. Les positions et les vitesses demandent AS float (32 bits) ou AS float64. AGK et DarkBASIC ont le même défaut.

  • Un nouvel enregistrement commence avec 0 dans les nombres, "" dans les chaînes, et des collections vides.
  • Écrire un champ convertit la valeur dans le type du champ : lives AS uint8 a changé 260 en 4.
  • Les noms de champs ignorent la casse, et deux champs du même nom sont une erreur de compilation : « Duplicate BASIC field in Player: … ». Deux types du même nom sont refusés aussi.
  • Un champ peut utiliser un type d'enregistrement déclaré plus loin dans le programme.
  • Un type de champ inconnu est une erreur de compilation : « Unknown BASIC field type ».
  • Le mot de fermeture est ENDTYPE. END TYPE (QBasic, VB) est refusé :
TYPE Point
  x
END TYPE

⚠️ Préférez AS aux sigils. Un champ écrit name$ ne garde son type que tant que le même nom n'est jamais employé sans le $, nulle part dans le programme. Une variable appelée name ailleurs suffit : le champ devient alors un nombre entier, et y ranger du texte arrête le programme. name AS string n'a pas ce problème.

⚠️ Évitez les noms de champ d'une lettre parmi b a w u l i q f d s c. Ces lettres sont des suffixes de type PureBasic. Dans une méthode, SELF.w = 10 est lu comme « déclarer une variable 16 bits », et le champ n'est jamais écrit. Appelez-le width.

Le suffixe de champ de PureBasic (x.f) n'est pas accepté dans un TYPE. Écrivez x AS float.

La forme sur une ligne#

TYPE Point x AS float y AS float ENDTYPE
TYPE Hero name AS string hp AS integer bag AS integer [2] ENDTYPE
p AS Point
p.x = 1.5
h AS Hero
h.bag[2] = 4
Console(Str(p.x) + " " + Str(h.bag[2]))   ' 1.5 4

Le TYPE entier tient sur une ligne quand ses champs sont simples : un nom, un AS <type> facultatif, des [bornes] facultatives. Les champs LIST, MAP et ARRAY et les méthodes demandent la forme longue.

Des enregistrements dans des enregistrements#

Un champ peut être un enregistrement. Un TYPE peut même être déclaré à l'intérieur d'un autre ; son nom reste utilisable partout.

TYPE Vec
  x AS float
  y AS float
ENDTYPE
TYPE Ship
  pos AS Vec
  vel AS Vec
ENDTYPE
s AS Ship
s.pos.x = 3
s.vel.y = -1.5
Console(Str(s.pos.x) + " " + Str(s.vel.y))   ' 3 -1.5

Les champs tableaux#

Indexez un champ tableau avec une paire de crochets par dimension : b.grid[1][2]. Les autres graphies compilent et tournent mal : b.grid(1, 2) arrête le programme à l'exécution, et b.grid[1, 2] atteint sans un mot une autre case.

TYPE Board
  ARRAY grid.i(2, 2)
  marks AS integer [4]
ENDTYPE
b AS Board
b.grid[1][2] = 5
b.marks[4] = 1
Console(Str(b.grid[1][2]) + " " + Str(b.marks.length))   ' 5 4

Les membres des tableaux (.length, .insert, .sort()…) marchent aussi sur les champs tableaux. Voir Tableaux, listes et dictionnaires.

Déclarer un enregistrement#

<nom> AS <Type>
<nom> AS <Type> = <enregistrement>
<nom> AS <Type> = NEW <Type>(<arguments>)
<nom> AS <Type>[<dernier indice>]
DIM <nom>(<dernier indice>) AS <Type>
NewList <nom>.<Type>()
NewMap <nom>.<Type>()
  • Une variable enregistrement doit être déclarée avec AS. Écrire q.x = 1 sur un nom jamais déclaré compile, puis arrête le programme : « q is not defined ».
  • Les noms de types ignorent la casse : hero AS ship convient.
  • Chaque déclaration crée un enregistrement neuf et exécute sa méthode New, s'il en a une. De même pour chaque case d'un tableau d'enregistrements, chaque AddElement sur une liste d'enregistrements, chaque nouvelle entrée de dictionnaire, et chaque champ enregistrement d'un nouvel enregistrement.

Atteindre un champ#

Où est l'enregistrementGraphie
une variablehero.fuel
un enregistrement dans un enregistrements.pos.x
une case de tableaufleet(2).fuel ou fleet[2].fuel
l'élément courant d'une listefoes().hp
une entrée de dictionnairebag("sword").qty

La graphie avec antislash hero\fuel appartient aux blocs WITH. En dehors, elle est refusée avec « Not a BASIC statement ».

⚠️ Une écriture par un élément de liste ou une entrée de dictionnaire n'est pas convertie dans le type du champ : foes().hp = 3.7 garde 3.7 dans un champ entier. Les écritures par une variable ou une case de tableau sont converties.

Les enregistrements sont des valeurs#

TYPE Vec
  x AS float
ENDTYPE
TYPE Ship
  pos AS Vec
  name AS string
ENDTYPE
a AS Ship
a.pos.x = 3
b AS Ship
b = a
b.pos.x = 9
Console(Str(a.pos.x) + " " + Str(b.pos.x))   ' 3 9

Affecter un enregistrement à une variable déclarée en fait une copie profonde. Les enregistrements, tableaux, listes et dictionnaires imbriqués sont copiés avec lui. Les deux enregistrements sont ensuite indépendants.

La même copie a lieu pour x AS T = autre, pour un élément de liste (l() = rec) et pour une entrée de dictionnaire (m("k") = rec). Passer un enregistrement à une FUNCTION le copie aussi, sauf si le paramètre est REF, voir Les fonctions.

⚠️ Trois affectations ne copient pas, et partagent à la place :

AffectationRésultat
fleet(1) = hero, une case de tableaula case et hero sont le même enregistrement
s.pos = v, un champ enregistrementle champ et v sont le même enregistrement
w = hero, avec w jamais déclaré AS Shipw et hero sont le même enregistrement

Après l'une d'elles, un changement par un nom se voit par l'autre. Pour ranger une copie indépendante dans une case de tableau ou un champ, copiez les champs un par un :

TYPE Vec
  x AS float
  y AS float
ENDTYPE
DIM path(1) AS Vec
v AS Vec
v.x = 1
v.y = 2
path(0).x = v.x
path(0).y = v.y
v.x = 7
Console(Str(path(0).x))   ' 1, la case a gardé sa propre valeur

Comparer des enregistrements. a = b sur deux enregistrements demande s'il s'agit du même enregistrement, pas si leurs champs sont égaux. Deux enregistrements distincts, même aux champs égaux, ne sont jamais =. Comparez les champs qui vous intéressent.

WITH#

WITH <enregistrement>
  \<champ> …
ENDWITH
TYPE Vec
  x AS float
  y AS float
ENDTYPE
TYPE Ship
  pos AS Vec
  name AS string
  fuel AS integer
ENDTYPE
s AS Ship
WITH s
  \name = "Comet"
  \fuel = 80
  \pos\x = 1.5
  \pos.y = 2.5
  Console(\name + " " + Str(\fuel) + " " + Str(\pos\x) + " " + Str(\pos\y))   ' Comet 80 1.5 2.5
ENDWITH
  • Dans le bloc, \champ signifie <enregistrement>.champ, n'importe où sur la ligne.
  • Une chaîne s'écrit \pos\x ou \pos.x.
  • Le bloc se ferme par ENDWITH ou END WITH.
  • L'enregistrement peut être n'importe quelle expression : WITH fleet(i), WITH foes().
  • Le texte des chaînes et des commentaires n'est pas touché : "C:\games" reste un chemin.
  • Les blocs WITH ne s'imbriquent pas. Un WITH dans un WITH est une erreur de compilation : « Nested WITH blocks are not supported ».

⚠️ L'expression de l'enregistrement est relue à chaque \. Le compilateur écrit l'expression devant chaque champ. Avec WITH fleet(i), changer i dans le bloc envoie les lignes suivantes vers une autre case.

WITH hero n'est pas WITH SPRITE s. Un seul mot après WITH fait un bloc d'enregistrement. Deux mots, dont le premier nomme une famille de commandes, font le raccourci de commandes : \position 100, 200 appelle alors SpritePosition(s, 100, 200). Voir Les raccourcis.

METHOD#

TYPE <Nom>
  <champs>
  METHOD <méthode>([<paramètres>])
    <instructions>
    [EXITMETHOD [<valeur>]]
  ENDMETHOD
ENDTYPE
TYPE Counter
  value AS integer
  METHOD Add(amount AS integer)
    SELF.value = SELF.value + amount
    EXITMETHOD SELF.value
  ENDMETHOD
  METHOD Reset()
    SELF.value = 0
  ENDMETHOD
ENDTYPE
c AS Counter
Console(Str(c.Add(5)))   ' 5
Console(Str(c.Add(3)))   ' 8
c.Reset()
Console(Str(c.value))   ' 0
  • Une méthode se déclare dans son TYPE, parmi les champs.
  • SELF est l'enregistrement sur lequel la méthode a été appelée. Écrire SELF.value modifie cet enregistrement, y compris quand c'est une case de tableau ou un élément de liste : counters(2).Reset(), counters().Add(3).
  • On l'appelle par enregistrement.méthode(arguments), comme instruction ou dans une expression. Les noms de méthodes ignorent la casse.
  • EXITMETHOD quitte la méthode. EXITMETHOD <valeur> rend une valeur. RETURN <valeur> fait la même chose.
  • Les parenthèses sont obligatoires, même vides : METHOD Reset().
  • Une méthode est une FUNCTION en dessous : paramètres typés, valeurs par défaut, variables LOCAL et OPTIONAL marchent tous. Voir Les fonctions.
  • Une méthode peut en appeler une autre par SELF : SELF.Reset().
  • La copie d'un enregistrement a les mêmes méthodes que l'original.
  • Dans un programme qui a une boucle DO, une variable du programme qu'une méthode utilise doit être déclarée GLOBAL, comme pour toute FUNCTION.

Refusés à la compilation : une METHOD hors d'un TYPE (« METHOD requires a containing TYPE »), une METHOD dans une METHOD, deux méthodes du même nom dans un TYPE, et une valeur après ENDMETHOD. Contrairement à ENDFUNCTION, ENDMETHOD ne prend pas de valeur, et END METHOD n'est pas une graphie :

TYPE Counter
  value AS integer
  METHOD Get()
  ENDMETHOD SELF.value
ENDTYPE

⚠️ N'appelez pas une méthode Length, Find, Sort, Insert ou Remove. Ces noms appartiennent aux membres des tableaux, et l'appel arrête le programme à l'exécution.

New : le constructeur#

Une méthode nommée New s'exécute chaque fois qu'un enregistrement de ce type est créé.

TYPE Counter
  value AS integer
  METHOD New(start AS integer = 0)
    SELF.value = start
  ENDMETHOD
ENDTYPE
a AS Counter
b AS Counter = NEW Counter(7)
Console(Str(a.value) + " " + Str(b.value))   ' 0 7
  • NEW <Type>(<arguments>) crée un enregistrement et passe les arguments à New. Cela marche dans une déclaration et dans une affectation ultérieure : b = NEW Counter(2).
  • Toutes les autres créations appellent New sans argument : x AS T, chaque case de DIM a(n) AS T, AddElement sur une liste d'enregistrements, une nouvelle entrée de dictionnaire, un champ enregistrement de ce type.
  • Une copie n'exécute pas New : b = a et x AS T = autre copient les champs tels quels.

⚠️ Donnez une valeur par défaut à chaque paramètre de New. Comme la plupart des créations ne passent pas d'argument, un paramètre typé sans valeur par défaut arrête le programme (« BASIC integer requires an integral value »), et un paramètre non typé laisse le champ sans valeur.

DELETE et la méthode Delete#

DELETE <variable enregistrement>
DELETE <tableau>[<indice>]
DELETE <enregistrement>.<champ>
alive = 0
TYPE Enemy
  hp AS integer
  METHOD New()
    alive = alive + 1
  ENDMETHOD
  METHOD Delete()
    alive = alive - 1
  ENDMETHOD
ENDTYPE
e AS Enemy
f AS Enemy
Console(Str(alive))   ' 2
DELETE e
Console(Str(alive))   ' 1
  • DELETE appelle la méthode Delete de l'enregistrement, s'il en a une, puis marque l'enregistrement comme supprimé.
  • Sur une variable, DELETE la vide aussi. Lire un de ses champs ensuite arrête le programme.
  • Sur une case de tableau ou un champ, la case reste, mais appeler une de ses méthodes arrête le programme : « BASIC record has been deleted ». Le chemin prend des crochets : DELETE fleet[2]. DELETE fleet(2) est refusé.
  • Delete ne s'exécute que par DELETE. Réaffecter une variable, ou atteindre la fin du programme, ne l'appelle pas.

Le Structure de PureBasic#

Structure … EndStructure est accepté comme une autre graphie de TYPE … ENDTYPE, forme sur une ligne comprise. Voir Les raccourcis.

Structure Size width height EndStructure
s AS Size
s.width = 2
Console(Str(s.width))   ' 2

Si vous venez d'un autre BASIC#

Vous écrivez peut-êtreDans aukimi
Type … End TypeTYPE … ENDTYPE
x.f dans une Structure (PureBasic)x AS float
p\x hors d'un With (PureBasic)p.x
AllocateStructure (PureBasic)p AS Point, ou NEW Point()
FreeStructure (PureBasic)DELETE p
END METHOD, ENDMETHOD valeurENDMETHOD, précédé de EXITMETHOD valeur
With imbriquésun seul WITH à la fois