Create: Computing AgesDoc Brass
Le langage Brass

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.

Brass
-- 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)
Écran
27 piles de lingot de fer
plein : true
Essayer une ligne à la fois

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).

Terminal
> 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.

Brass
--[[
  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)
Écran
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 :

typeexemplessert à
nilnil« pas de valeur » : une variable jamais remplie, un champ absent, un emplacement vide
booleantrue, falseoui ou non : le levier est-il activé, le réservoir est-il plein
number64, -3.5, 1e3, 0xFFdes 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
functionprint, function(x) return x * 2 enddu code qu'on appelle, voir Fonctions

type donne le type d'une valeur, sous forme de chaîne :

Brass
print(type(64), type("top"), type(true))
print(type(nil), type({}), type(print))
Écran
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 :

écriturevaleur
64, -12des nombres entiers
3.5, .5des nombres à virgule (la virgule s'écrit avec un point)
1e3, 2.5e-3avec un exposant : 1e3 vaut 1000, 2.5e-3 vaut 0,0025
0xFFen hexadécimal : 255
Brass
print(12, 3.5, .5, 1e3, 0xFF)
print(7 / 2, 10 / 2, 2 ^ 10)
Écran
12  3.5 0.5 1000    255
3.5 5   1024

L'affichage des nombres

  • Un nombre entier s'affiche **sans .0** : 10 / 2 affiche 5, pas 5.0.
  • Au plus 14 chiffres significatifs s'affichent : 1 / 3 affiche 0.33333333333333, math.pi affiche 3.1415926535898.
  • Les très grands et très petits nombres prennent un exposant : 1e15 affiche 1e+15, 0.00001 affiche 1e-05.
  • Diviser par zéro n'est pas une erreur. Cela donne inf (l'infini), -inf, ou nan (« pas un nombre », pour 0 / 0) : vérifiez vous-même un diviseur qui peut valoir zéro.
Brass
print(1 / 3, 1e15, 2 ^ 53)
print(1 / 0, -1 / 0, 0 / 0)
print(0.1 + 0.2, 0.1 + 0.2 == 0.3)
Écran
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érateursensexemplerésultat
+addition64 + 1680
-soustraction (et -x, l'opposé de x)64 - 1648
*multiplication27 * 641728
/division, toujours à virgule7 / 23.5
//division entière : on divise, puis on arrondit vers le bas7 // 23
%reste (modulo)7 % 21
^puissance2 ^ 101024

// 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).

Brass
local objets = 200
print(objets // 64 .. " piles et " .. objets % 64 .. " objets")
print(5 % 4, 8 % 4, -1 % 4)
print(-7 // 2, 2 ^ 0.5)
Écran
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équencedonne
\nun retour à la ligne
\tune tabulation : à l'écran, elle avance jusqu'à la prochaine colonne multiple de 4
\\une barre oblique inverse
\" et \'un guillemet
\xNNle caractère de code hexadécimal NN : \x41 donne A
\r, \0retour 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 :

Brass
print("Fer :\t64\nOr :\t8")
print('Appuyez sur "Démarrer"')
print([[
+-----------------+
| COFFRE RENFORCÉ |
+-----------------+]])
Écran
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 :

Brass
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")
end
Écran
0 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.

Brass
local machines = {presse = 64}
print(machines.presse, machines.mixeur)
print(jamais_rempli)
Écran
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 : Compte et compte sont 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 :

Brass
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)
Écran
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 :

Brass
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)
Écran
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érateurvrai 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" == 10 vaut false.
  • <, <=, >, >= comparent deux nombres, ou deux chaînes dans l'ordre des caractères ("Z" < "a" vaut true : les majuscules passent avant). Un nombre contre une chaîne est une erreur : attempt to compare number with string.
  • = range, == compare. if x = 5 then est une erreur de compilation : 'then' expected near '='.
  • 1 < x < 10 ne veut pas dire ce qu'on croit : 1 < x donne un booléen, puis true < 10 est une erreur. Écrivez 1 < x and x < 10.

Logique : and, or, not

expressionrésultat
not atrue si a vaut false ou nil, sinon false
a and ba si a est faux ou nil, sinon b
a or ba 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 y donne x quand cond est vrai, sinon y. Par exemple rs.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.

Brass
local plein = true
print(plein and "PLEIN" or "ok")
local rester_ferme = true
print(rester_ferme and false or "ouvert")  -- on voulait false
Écran
PLEIN
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 faibleor
Brass
print(2 + 3 * 4, (2 + 3) * 4)
print(-2 ^ 2, 2 ^ 3 ^ 2)
print("total : " .. 60 + 4)
print(not 1 == 2)
Écran
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.

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 :

Brass
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"))
Écran
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.

Brass
local stock
print(stock + 1)
Écran
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 :

messagecause habituelleque faire
attempt to perform arithmetic on a nil valueune variable qui n'a jamais reçu de valeur, un nom mal orthographié, un champ de table absentvérifiez l'orthographe, donnez une valeur de départ (local compte = 0)
attempt to perform arithmetic on a string valuedu texte dans un calcul, comme "10" + 1convertissez avec tonumber
attempt to concatenate a nil value"Stock : " .. compte alors que compte vaut niltostring(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éfinitcorrigez le nom ou l'ordre
attempt to index a nil value (local 'coffre')coffre.count() alors que coffre vaut nil, par exemple un bloc absenttestez d'abord if coffre == nil then
attempt to compare number with stringtape < 10 avec une chaîne venue de read()convertissez avec tonumber
attempt to get length of a nil value#liste alors que liste vaut nilvé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.