Valeurs et variables
Commentaires, nombres, texte, vrai et faux, nil, variables, et les opérateurs qui les combinent.
Un programme travaille sur des valeurs : le nombre de lingots de fer dans un coffre à objet renforcé, le nom d'un joueur, l'état d'un levier. Cette page présente les sortes de valeurs que connaît Brass, la façon de les garder dans des variables et de les combiner avec des opérateurs. Tout le reste s'appuie dessus : les conditions et les boucles, les fonctions et les tables.
Si vous n'avez jamais programmé, lisez la page dans l'ordre et essayez les exemples : écrivez-les dans un fichier avec edit test, lancez-les avec test (voir le shell et l'éditeur). Si vous connaissez déjà Lua, parcourez-la puis lisez Brass pour les habitués de Lua : les différences sont peu nombreuses, mais elles comptent.
-- Combien de piles remplissent un coffre renforcé ?
local lingots = 1728 -- un nombre
local objet = "lingot de fer" -- une chaîne (du texte)
local plein = lingots >= 1728 -- un booléen (vrai ou faux)
print(lingots // 64 .. " piles de " .. objet)
print("plein :", plein)27 piles de lingot de fer plein : true
Tapez brass à l'invite pour ouvrir l'interpréteur interactif : chaque ligne tapée s'exécute aussitôt, et la valeur d'une expression s'affiche. exit en sort. Chaque ligne est un petit programme à part : une variable local est oubliée à la ligne suivante, utilisez-y plutôt une globale (x = 5).
> brass Brass 1.0 - type 'exit' to leave. brass> 1728 // 64 27 brass> exit
Commentaires
Un commentaire est une note pour les humains : l'ordinateur l'ignore. -- commence un commentaire qui va jusqu'au bout de la ligne, et --[[ ... ]] est un commentaire en bloc qui peut tenir sur plusieurs lignes.
--[[
Chaîne de presses mécaniques
levier à gauche, lampe d'alarme au-dessus
]]
local rpm = 64 -- vitesse de la presse
-- print("debug : " .. rpm)
print(rpm)64
Servez-vous des commentaires pour expliquer pourquoi le code fait quelque chose (« le coffre renforcé contient 1728 lingots »), pas ce qu'il fait : le code le dit déjà. Un commentaire sert aussi à couper une ligne le temps d'un essai, comme le print ci-dessus. Dans l'éditeur, Ctrl+/ (ou Ctrl+: en AZERTY) commente ou décommente les lignes sélectionnées.
Un commentaire en bloc se termine au premier ]] : il ne peut donc pas contenir ]] lui-même.
Les six types
Chaque valeur a un type. Brass en a six :
| type | exemples | sert à |
|---|---|---|
nil | nil | « pas de valeur » : une variable jamais remplie, un champ absent, un emplacement vide |
boolean | true, false | oui ou non : le levier est-il activé, le réservoir est-il plein |
number | 64, -3.5, 1e3, 0xFF | des quantités, des vitesses, des coordonnées, une force de redstone |
string | "fer", 'top' | du texte : noms d'objets, côtés, messages |
table | {1, 2, 3}, {rpm = 64} | des listes et des fiches, voir Tables |
function | print, function(x) return x * 2 end | du code qu'on appelle, voir Fonctions |
type donne le type d'une valeur, sous forme de chaîne :
print(type(64), type("top"), type(true))
print(type(nil), type({}), type(print))number string boolean nil table function
Ce sont les valeurs qui ont un type, pas les variables : une même variable peut contenir un nombre maintenant et une chaîne plus tard. Quand une valeur vient de l'extérieur (un message d'un autre ordinateur, une ligne tapée par le joueur), type(v) == "number" vérifie qu'elle est bien ce que vous attendez avant de vous en servir.
Nombres
Brass n'a qu'une sorte de nombre : un nombre à virgule flottante sur 64 bits (un « double »). Il n'y a pas de type entier à part : 7 / 2 vaut 3.5, et un nombre entier est simplement un nombre sans rien après la virgule. On écrit les nombres de plusieurs façons :
| écriture | valeur |
|---|---|
64, -12 | des nombres entiers |
3.5, .5 | des nombres à virgule (la virgule s'écrit avec un point) |
1e3, 2.5e-3 | avec un exposant : 1e3 vaut 1000, 2.5e-3 vaut 0,0025 |
0xFF | en hexadécimal : 255 |
print(12, 3.5, .5, 1e3, 0xFF)
print(7 / 2, 10 / 2, 2 ^ 10)12 3.5 0.5 1000 255 3.5 5 1024
L'affichage des nombres
- Un nombre entier s'affiche **sans
.0** :10 / 2affiche5, pas5.0. - Au plus 14 chiffres significatifs s'affichent :
1 / 3affiche0.33333333333333,math.piaffiche3.1415926535898. - Les très grands et très petits nombres prennent un exposant :
1e15affiche1e+15,0.00001affiche1e-05. - Diviser par zéro n'est pas une erreur. Cela donne
inf(l'infini),-inf, ounan(« pas un nombre », pour0 / 0) : vérifiez vous-même un diviseur qui peut valoir zéro.
print(1 / 3, 1e15, 2 ^ 53)
print(1 / 0, -1 / 0, 0 / 0)
print(0.1 + 0.2, 0.1 + 0.2 == 0.3)0.33333333333333 1e+15 9.007199254741e+15 inf -inf nan 0.3 false
Les nombres entiers sont exacts jusqu'à 2^53 (environ 9 millions de milliards) : compter des objets, des ticks ou des blocs ne perd jamais en précision. Les nombres à virgule sont des approximations, comme sur tout ordinateur : 0.1 + 0.2 dépasse 0.3 d'un cheveu. Le résultat s'affiche 0.3 parce que seuls 14 chiffres apparaissent, mais 0.1 + 0.2 == 0.3 vaut false. Comparez plutôt les nombres à virgule avec une tolérance : math.abs(a - b) < 0.0001.
Pour afficher un nombre fixe de décimales (12.50), utilisez string.format("%.2f", x). Pour arrondir, voyez math.
Arithmétique
| opérateur | sens | exemple | résultat |
|---|---|---|---|
+ | addition | 64 + 16 | 80 |
- | soustraction (et -x, l'opposé de x) | 64 - 16 | 48 |
* | multiplication | 27 * 64 | 1728 |
/ | division, toujours à virgule | 7 / 2 | 3.5 |
// | division entière : on divise, puis on arrondit vers le bas | 7 // 2 | 3 |
% | reste (modulo) | 7 % 2 | 1 |
^ | puissance | 2 ^ 10 | 1024 |
// et % vont ensemble : 200 objets font 200 // 64 piles pleines et 200 % 64 objets en plus. Avec des nombres négatifs, // arrondit vers moins l'infini et le résultat de % prend le signe du diviseur : % est donc parfait pour tout ce qui tourne en rond (quatre lampes, les étapes d'une séquence, les heures d'une journée).
local objets = 200
print(objets // 64 .. " piles et " .. objets % 64 .. " objets")
print(5 % 4, 8 % 4, -1 % 4)
print(-7 // 2, 2 ^ 0.5)3 piles et 8 objets 1 0 3 -4 1.4142135623731
Chaînes de caractères
Une chaîne est un morceau de texte, entre guillemets doubles "..." ou simples '...'. Les deux reviennent au même : choisissez celui qui vous laisse écrire l'autre à l'intérieur sans souci ('Appuyez sur "Démarrer"').
Entre guillemets, une barre oblique inverse commence une séquence d'échappement, une façon d'écrire un caractère spécial :
| séquence | donne |
|---|---|
\n | un retour à la ligne |
\t | une tabulation : à l'écran, elle avance jusqu'à la prochaine colonne multiple de 4 |
\\ | une barre oblique inverse |
\" et \' | un guillemet |
\xNN | le caractère de code hexadécimal NN : \x41 donne A |
\r, \0 | retour chariot, caractère nul (rarement utiles) |
Toute autre séquence est une erreur de compilation (invalid escape sequence '\q'), et une chaîne doit se terminer sur la ligne où elle commence (unfinished string).
Une chaîne longue s'écrit entre [[ et ]]. Elle peut tenir sur plusieurs lignes et n'a pas de séquences d'échappement (les barres obliques inverses restent telles quelles). Un retour à la ligne juste après le [[ est ignoré. C'est pratique pour les écrans d'aide et les dessins :
print("Fer :\t64\nOr :\t8")
print('Appuyez sur "Démarrer"')
print([[
+-----------------+
| COFFRE RENFORCÉ |
+-----------------+]])Fer : 64 Or : 8 Appuyez sur "Démarrer" +-----------------+ | COFFRE RENFORCÉ | +-----------------+
On colle des chaînes avec .. (voir la concaténation), et # donne leur longueur : #"fer" vaut 3. Une chaîne ne change jamais : des fonctions comme string.upper renvoient une nouvelle chaîne. On peut les appeler comme des méthodes, ("fer"):upper() ou nom:upper(). Le guide Chaînes et la référence string les présentent toutes.
Vrai et faux
Un booléen vaut true (vrai) ou false (faux). Les comparaisons en produisent (stock < 64), et les conditions (if, while, and, or, not) s'en servent.
Mais une condition accepte n'importe quelle valeur, et la règle est courte : **seuls false et nil sont faux. Tout le reste est vrai, y compris 0 et la chaîne vide "".** C'est différent du C, de JavaScript ou de Python, et cela compte avec la redstone : rs.get renvoie un nombre de 0 à 15, donc if rs.get("left") then est toujours vrai. Comparez le nombre :
local signal = 0 -- ce que donne rs.get pour un côté sans courant
if signal then
print("0 compte comme vrai !")
end
if signal > 0 then
print("alimenté")
else
print("pas de courant")
end0 compte comme vrai ! pas de courant
nil
nil veut dire « rien ici ». Une variable à qui on n'a jamais donné de valeur, un champ de table qui n'existe pas, le résultat d'une fonction qui ne renvoie rien : tout cela vaut nil. Les lire n'est pas une erreur ; s'en servir dans un calcul en est une.
local machines = {presse = 64}
print(machines.presse, machines.mixeur)
print(jamais_rempli)64 nil nil
Donner nil à une variable ou à un champ efface sa valeur. Pour le tester, écrivez if x == nil then. La forme plus courte if not x then attrape aussi false, ce qui ne pose pas de problème tant que false n'a pas de sens pour x.
Variables
Une variable est un nom donné à une valeur. nom = valeur range la valeur ; écrire le nom plus loin la relit.
- Un nom est fait de lettres, de chiffres et de
_, et ne commence pas par un chiffre :stock,rpm_max,cote2. - Les majuscules comptent :
Compteetcomptesont deux variables différentes. - Ces mots sont réservés et ne peuvent pas servir de nom :
and break do else elseif end false for function if in local nil not or repeat return then true until while. - Évitez les noms des bibliothèques et des fonctions intégrées (
table,string,type,print...) : votre variable les masquerait.
Locale ou globale
local x = 1 crée une variable locale. Elle existe de cette ligne jusqu'à la fin du bloc où elle est écrite : le fichier, une fonction, une boucle, une branche d'un if. Un nom déclaré à nouveau avec local dans un bloc intérieur est une nouvelle variable, qui cache l'ancienne jusqu'à la fin de ce bloc :
local niveau = 1 -- tout le fichier voit celle-ci
do
local niveau = 2 -- une autre variable, seulement dans do ... end
print("dedans :", niveau)
end
print("dehors :", niveau)dedans : 2 dehors : 1
Sans local, x = 1 écrit une variable globale. Les globales sont visibles depuis toutes les fonctions et tous les fichiers du programme, et elles restent dans la mémoire de l'ordinateur après la fin du programme, jusqu'à son redémarrage. Lire une globale qui n'existe pas donne nil.
**Utilisez local par défaut.** Une locale ne peut pas être modifiée par erreur depuis un autre fichier ou par le programme suivant, et on voit en lisant le code d'où elle vient. Les locales d'une fonction disparaissent en plus quand la fonction se termine, et leur mémoire avec elles. Gardez les globales pour la rare valeur qu'on partage exprès.
Une local écrite au premier niveau d'un fichier, hors de toute fonction, est une locale de fichier. Toutes les fonctions de ce fichier écrites après elle peuvent la lire et la modifier : c'est la bonne place pour l'état de votre programme (un compteur, les réglages en haut du fichier). C'est important en Brass, car une fonction ne peut pas se servir des locales de la fonction qui l'entoure : voir la règle de portée.
local x tout seul déclare la variable avec la valeur nil.
Affectation multiple
On peut affecter plusieurs variables en une ligne, séparées par des virgules. Les valeurs manquantes donnent nil, les valeurs en trop sont ignorées. Toutes les valeurs de droite sont calculées avant qu'une seule variable ne change, ce qui permet d'échanger deux variables en une ligne :
local x, y, z = 120, 64, -35 -- une position
print(x, y, z)
local entree, sortie = "lampe", "piston"
entree, sortie = sortie, entree -- échange
print(entree, sortie)
local a, b = 1
print(a, b)120 64 -35 piston lampe 1 nil
En Brass, une fonction ne renvoie qu'une valeur : local a, b = f() laisse donc toujours b à nil. Une fonction qui a plusieurs choses à rendre renvoie une table.
Opérateurs
Comparaison
| opérateur | vrai quand |
|---|---|
== | les deux valeurs sont égales |
~= ou != | elles sont différentes (les deux écritures marchent) |
<, <=, >, >= | plus petit, au plus, plus grand, au moins |
- Les nombres, les chaînes et les booléens se comparent par leur valeur. Les tables et les fonctions se comparent par identité : deux tables ne sont égales que si c'est la même table (voir les références).
- Des valeurs de types différents ne sont jamais égales :
"10" == 10vautfalse. <,<=,>,>=comparent deux nombres, ou deux chaînes dans l'ordre des caractères ("Z" < "a"vauttrue: les majuscules passent avant). Un nombre contre une chaîne est une erreur :attempt to compare number with string.=range,==compare.if x = 5 thenest une erreur de compilation :'then' expected near '='.1 < x < 10ne veut pas dire ce qu'on croit :1 < xdonne un booléen, puistrue < 10est une erreur. Écrivez1 < x and x < 10.
Logique : and, or, not
| expression | résultat |
|---|---|
not a | true si a vaut false ou nil, sinon false |
a and b | a si a est faux ou nil, sinon b |
a or b | a si a n'est ni faux ni nil, sinon b |
and et or s'arrêtent dès que la réponse est connue : la partie droite n'est calculée que si besoin. coffre ~= nil and coffre.count() > 0 n'appelle jamais coffre.count sur un coffre absent.
Comme ils renvoient l'une de leurs deux valeurs (et pas seulement true ou false), ils donnent deux tournures très courantes :
- Une valeur par défaut :
local cote = cote_choisi or "back". - Un choix en une ligne :
cond and x or ydonnexquandcondest vrai, sinony. Par exemplers.set("top", plein and 15 or 0).
La seconde tournure a un piège : quand x vaut lui-même false ou nil, le or prend le relais et on obtient toujours y.
local plein = true
print(plein and "PLEIN" or "ok")
local rester_ferme = true
print(rester_ferme and false or "ouvert") -- on voulait falsePLEIN ouvert
Quand la valeur du milieu peut valoir false ou nil, écrivez un vrai if ... else ... end (voir Conditions et boucles).
Concaténation et longueur
.. colle deux chaînes en une nouvelle. Les nombres sont convertis en texte au passage : "Stock : " .. 64 donne "Stock : 64". Toute autre valeur (nil, un booléen, une table) est une erreur, attempt to concatenate a nil value : passez-la d'abord dans tostring. Laissez des espaces autour de .. à côté des nombres, c'est plus lisible : 1 .. 2 donne "12".
# donne la longueur d'une chaîne (#"fer" vaut 3) ou d'une liste (#{"a", "b"} vaut 2 ; voir les listes).
Priorité
Quand une expression mélange des opérateurs, ceux du haut de ce tableau s'appliquent en premier. Les opérateurs d'une même ligne s'appliquent de gauche à droite, sauf ^ et .., qui vont de droite à gauche.
| priorité | opérateurs |
|---|---|
| la plus forte | ^ |
not, #, - (devant une valeur) | |
*, /, //, % | |
+, - | |
.. | |
==, ~=, !=, <, <=, >, >= | |
and | |
| la plus faible | or |
print(2 + 3 * 4, (2 + 3) * 4)
print(-2 ^ 2, 2 ^ 3 ^ 2)
print("total : " .. 60 + 4)
print(not 1 == 2)14 20 -4 512 total : 64 false
Les deux dernières lignes sont des pièges : + passe avant .., donc 60 + 4 est calculé d'abord (c'est ce qu'on veut ici), et not passe avant ==, donc not 1 == 2 se lit (not 1) == 2, c'est-à-dire false == 2. Écrivez not (x == y), ou tout simplement x ~= y. Dans le doute, ajoutez des parenthèses : elles ne coûtent rien.
Passer d'un type à l'autre
Brass ne transforme jamais un texte en nombre de lui-même : "10" + 1 est une erreur (attempt to perform arithmetic on a string value), là où Lua donnerait 11. La seule conversion automatique va dans l'autre sens : .. accepte les nombres.
tonumber(s)lit un nombre dans une chaîne et renvoienilquand le texte n'est pas un nombre. Les espaces autour du nombre sont acceptés, tout comme les décimales, les exposants et l'hexadécimal en0x. Avec une base,tonumber("ff", 16)vaut255ettonumber("101", 2)vaut5.tostring(v)transforme n'importe quelle valeur en texte :tostring(nil)donne"nil",tostring(true)donne"true",tostring(12.0)donne"12". Une table donne son adresse, du genretable: 0x1b6d3586.
Le texte tapé par le joueur (read) ou reçu d'un autre ordinateur est une chaîne. Convertissez-le avant de calculer avec, et prévoyez le cas où ce n'est pas un nombre :
local tape = "32" -- ce que read() pourrait renvoyer
local rpm = tonumber(tape)
if rpm == nil then
print("tapez un nombre, s'il vous plaît")
else
print("vitesse doublée : " .. rpm * 2 .. " tr/min")
end
print(tonumber("12 tr/min"), tonumber(" 12 "), tonumber("0x1F"))vitesse doublée : 64 tr/min nil 12 31
Les erreurs que vous croiserez
Quand quelque chose tourne mal pendant l'exécution, le programme s'arrête et affiche un message en rouge : le fichier, la ligne, et ce qui s'est passé. Ici le programme s'appelle snippet ; le vôtre affiche le nom de son fichier.
local stock
print(stock + 1)snippet:2: attempt to perform arithmetic on a nil v alue
(L'écran d'un Micro-ordinateur fait 51 caractères de large : un long message passe à la ligne.) Les messages les plus fréquents au début :
| message | cause habituelle | que faire |
|---|---|---|
attempt to perform arithmetic on a nil value | une variable qui n'a jamais reçu de valeur, un nom mal orthographié, un champ de table absent | vérifiez l'orthographe, donnez une valeur de départ (local compte = 0) |
attempt to perform arithmetic on a string value | du texte dans un calcul, comme "10" + 1 | convertissez avec tonumber |
attempt to concatenate a nil value | "Stock : " .. compte alors que compte vaut nil | tostring(compte), ou (compte or 0) |
attempt to call a nil value (global 'prnt') | une fonction mal orthographiée, ou appelée avant la ligne qui la définit | corrigez le nom ou l'ordre |
attempt to index a nil value (local 'coffre') | coffre.count() alors que coffre vaut nil, par exemple un bloc absent | testez d'abord if coffre == nil then |
attempt to compare number with string | tape < 10 avec une chaîne venue de read() | convertissez avec tonumber |
attempt to get length of a nil value | #liste alors que liste vaut nil | vérifiez d'où vient la liste |
Les fautes dans le code lui-même sont repérées avant toute exécution, ce sont des erreurs de compilation : 'end' expected (to close 'if' at line 2) near <eof> (un end oublié), 'then' expected near 'print', unfinished string, unexpected symbol '!' (use 'not'). Le guide Erreurs explique comment les lire, rattraper les erreurs d'exécution avec pcall et lever les vôtres avec error ; la référence des erreurs donne tous les messages.