Create: Computing AgesDoc Brass
Bibliothèques

Fonctions globales

print, read, sleep, conversions, boucles, erreurs et imports : les fonctions que tout programme appelle sans nom de bibliothèque.

Tous les ordinateurs

Ces fonctions sont toujours là : pas de nom de bibliothèque devant, rien à charger, et tous les ordinateurs les ont, du Calculateur à tubes à l'Ordinateur moderne. Elles affichent du texte et lisent le clavier (print, read), mettent le programme en pause (sleep), convertissent et examinent des valeurs (tonumber, type), parcourent des tables (pairs, ipairs), gèrent les erreurs (pcall) et découpent un programme en plusieurs fichiers (import).

Brass
local lignes = {"12", "15", "oups", "9"}   -- mesures lues dans un journal
local total = 0
for i, texte in ipairs(lignes) do
  local n = tonumber(texte)
  if n then
    total = total + n
  else
    print("la ligne " .. i .. " n'est pas un nombre : " .. texte)
  end
end
print("total", total)
Écran
la ligne 3 n'est pas un nombre : oups
total   36
Ne réutilisez pas leurs noms

Une variable créée sans local est elle aussi une globale, rangée au même endroit que ces fonctions. Appelez une variable type ou print, et la fonction disparaît pour tout le programme : type = peripheral.type("left") casse tous les appels suivants à type(...). Choisissez un autre nom (genre, typeBloc) ou utilisez local.

Fonctions
print(...)Affiche des valeurs à l'écran, puis va à la ligne.
write(...)Affiche du texte sans aller à la ligne.
read([mask])Attend une ligne tapée au clavier et la renvoie.
sleep(seconds)Met le programme en pause pendant un certain nombre de secondes.
tostring(value)Convertit une valeur en texte.
tonumber(value [, base])Convertit un texte en nombre, ou renvoie nil.
type(value)Donne le type d'une valeur : "nil", "number", "string", "boolean", "table" ou "function".
pairs(t)Parcourt toutes les clés et valeurs d'une table : for k, v in pairs(t) do.
ipairs(t)Parcourt t[1], t[2]... jusqu'au premier nil.
error(message)Arrête le programme avec une erreur.
pcall(f, ...)Appelle une fonction et attrape ses erreurs : vous obtenez {ok=true, value=...} ou {ok=false, error="..."}.
assert(value [, message])Arrête le programme avec une erreur si la valeur est false ou nil ; sinon la renvoie.
import(path)Exécute un autre fichier une seule fois et renvoie ce qu'il renvoie : import "lib/outils".
require(path)Comme import.
argLes mots tapés après le nom du programme à l'invite, sous forme de liste de textes.

Écran et clavier

print et write écrivent là où se trouve le curseur, avec les couleurs choisies par term.set_fg et term.set_bg. Quand le texte atteint le bord droit, il continue au début de la ligne suivante, coupé n'importe où (pas entre deux mots). Quand le curseur dépasse la dernière ligne, tout l'écran remonte d'une ligne. Pour écrire à un endroit précis sans retour à la ligne ni défilement, utilisez term.set_cursor et term.write (voir term).

#

print(...)

⚙ coût 1 par 16 caractères

Affiche des valeurs à l'écran, puis va à la ligne.

Paramètres
values any
autant de valeurs que vous voulez, de n'importe quel type

Chaque valeur est convertie comme le fait tostring : les nombres sans .0 inutile, true, false, nil. Plusieurs valeurs sont séparées par une tabulation, qui avance jusqu'à la prochaine colonne multiple de 4 : pratique pour jeter un œil à quelques nombres, mais utilisez string.format pour de vraies colonnes. print() sans rien affiche une ligne vide.

Brass
print("Lingots de fer :", 128)
print("En marche", true, nil)
print(10, 200, 3000)
print()
print("fini")
Écran
Lingots de fer :    128
En marche   true    nil
10  200 3000

fini

Un "\n" dans le texte commence une nouvelle ligne, et une ligne plus longue que l'écran (51 colonnes sur un Micro-ordinateur, 40 sur un Calculateur à tubes ou un Microcontrôleur, 64 sur un Ordinateur moderne) continue sur la suivante :

Brass
print("Ligne 1\nLigne 2")
print(string.rep("=", 60))
Écran
Ligne 1
Ligne 2
===================================================
=========

Les caractères impossibles à afficher (les codes sous 32, sauf la tabulation et le retour à la ligne) apparaissent comme ?.

Afficher prend du temps

Un appel coûte une instruction, plus une par tranche de 16 caractères affichés. Une ligne pleine de 51 caractères coûte 4 : rien pour un Ordinateur moderne, mais un Calculateur à tubes n'exécute que 20 instructions par tick, et un écran rempli de texte lui prend quelques ticks. Voir Vitesse, mémoire et limites.

Voir aussi write() term.write() tostring()

#

write(...)

⚙ coût 1 par 16 caractères

Affiche du texte sans aller à la ligne.

Paramètres
values any
autant de valeurs que vous voulez, de n'importe quel type

Les valeurs sont converties en texte et écrites l'une après l'autre, sans rien entre elles (contrairement à print, qui met une tabulation). Le curseur reste au bout : le write, print ou read suivant continue sur la même ligne. Tabulations, retours à la ligne, coupure en bord d'écran et défilement marchent comme avec print.

Brass
write("Fonte")
for i = 1, 3 do
  write(".")
end
write(" terminé", "\n")
write("Lot ", 4, " sur ", 10)
print()
Écran
Fonte... terminé
Lot 4 sur 10

Son usage le plus courant : la question avant un read, la réponse se tape juste derrière.

Brass
write("Combien de caisses ? ")
local reponse = read()

Voir aussi print() read() term.write()

#

read([mask])

→ string⏸ Attend

Attend une ligne tapée au clavier et la renvoie.

Paramètres
mask string facultatif
un caractère affiché à la place de chaque caractère tapé (seul le premier caractère compte)
Renvoie
string
la ligne tapée, sans la touche Entrée

Le programme s'arrête dans read jusqu'à ce qu'un joueur, dans l'écran de l'ordinateur, tape une ligne et appuie sur Entrée. Le texte s'affiche là où se trouve le curseur : une question écrite avec write juste avant reste sur la même ligne. Après Entrée, le curseur passe au début de la ligne suivante.

Brass
write("Nom de la nouvelle gare : ")
local nom = read()
print("Gare '" .. nom .. "' enregistrée.")

Ce que vous récupérez :

  • Toujours un texte, même si le joueur a tapé des chiffres : convertissez-le avec tonumber avant de calculer.
  • "" (un texte vide) si le joueur appuie sur Entrée sans rien taper.
  • 1024 caractères au plus. Une ligne plus longue que la place restante à l'écran défile de côté pendant la frappe.

Le joueur peut corriger la ligne avant d'appuyer sur Entrée : Retour arrière, Suppr, les flèches gauche et droite, Début et Fin. Ctrl+V colle le presse-papiers, jusqu'à son premier retour à la ligne. Les flèches haut et bas et Tab ne font rien ici : l'historique et la complétion n'existent qu'à l'invite du shell.

Cacher un mot de passe. Avec un masque, chaque caractère tapé s'affiche sous la forme de ce caractère. Seul son premier caractère sert : read("*") et read("*#") affichent tous deux *. Un masque vide "" ne cache rien, et un masque qui n'est pas un texte arrête le programme avec bad argument #1 to 'read' (string expected, got number).

Brass
write("Mot de passe : ")
local code = read("*")
if code == "laiton42" then
  print("Bienvenue")
  rs.set("left", true)    -- ouvre la porte à pistons de gauche
  sleep(3)
  rs.set("left", false)
else
  print("Mot de passe incorrect")
end

La recette Porte à code construit une porte complète autour de ce principe.

Un menu. Affichez les choix, lisez un numéro, agissez, recommencez. L'image ci-dessous montre l'écran pendant que read attend :

Brass
local choix = {"Démarrer la ligne de presses", "Arrêter la ligne de presses", "Quitter"}
while true do
  term.clear()
  term.set_cursor(1, 1)
  term.set_fg(term.colors.yellow)
  print("== Ligne de presses mécaniques ==")
  term.set_fg(term.colors.white)
  for i, texte in ipairs(choix) do
    print(i .. ". " .. texte)
  end
  print()
  write("Votre choix : ")
  local n = tonumber(read())
  if n == 1 then
    rs.set("back", false)   -- un embrayage non alimenté laisse tourner l'arbre
    print("Ligne démarrée")
  elseif n == 2 then
    rs.set("back", true)    -- un embrayage alimenté l'arrête
    print("Ligne arrêtée")
  elseif n == 3 then
    break
  else
    print("Tapez 1, 2 ou 3")
  end
  sleep(1)
end
Écran
Écran
read() et les autres évènements

Pendant que read attend, votre programme ne fait rien d'autre. Seuls les évènements du clavier (char, key et paste) vont dans la ligne. Tous les autres (un changement de redstone, une minuterie, un message réseau, un clic) sont gardés dans la file, dans l'ordre, et le prochain os.pull_event les reçoit. Les touches frappées avant l'appel à read, pendant un sleep par exemple, sont gardées elles aussi : read les utilise dès qu'il démarre (un os.pull_event avec un filtre, au contraire, jette les évènements qu'il saute, touches comprises). Pour réagir à la redstone pendant qu'un joueur tape, lisez vous-même le clavier avec os.pull_event (voir Évènements et Clavier).

Ctrl+T (ou le bouton Stopper) arrête le programme même pendant qu'il attend dans read.

Voir aussi write() tonumber() os.pull_event()

Attendre

#

sleep(seconds)

⏸ Attend

Met le programme en pause pendant un certain nombre de secondes.

Paramètres
seconds number
la durée de l'attente, en secondes (décimales permises)

Minecraft avance de 20 ticks par seconde, et l'ordinateur compte ses attentes en ticks : sleep attend secondes × 20 ticks, arrondi au-dessus à un tick entier, et toujours au moins un tick.

appelticksdurée réelle
sleep(1)201 s
sleep(0.5)100,5 s
sleep(0.07)20,1 s
sleep(0) ou un nombre négatif10,05 s
Brass
local function ticks_de(secondes)
  local debut = os.time()
  sleep(secondes)
  return os.time() - debut
end
print(ticks_de(1), ticks_de(0.5), ticks_de(0.07), ticks_de(0))
Écran
20  10  2   1

Un compte à rebours avant de fermer un portail :

Brass
for s = 5, 1, -1 do
  print("Fermeture du portail dans " .. s .. " s")
  sleep(1)
end
rs.set("top", true)   -- le piston mécanique ferme le portail
print("Portail fermé")
Écran
Fermeture du portail dans 5 s
Fermeture du portail dans 4 s
Fermeture du portail dans 3 s
Fermeture du portail dans 2 s
Fermeture du portail dans 1 s
Portail fermé

Pendant qu'il dort, l'ordinateur n'exécute aucune instruction, et le reste des instructions du tick en cours n'est pas gardé pour plus tard. Une boucle avec un sleep dedans est donc la bonne façon de répéter un travail à intervalles réguliers sans gaspiller le processeur, et une boucle avec sleep(0) tourne au plus 20 fois par seconde. Le temps est celui du monde : la vitesse de rotation de l'ordinateur ne le change pas, un serveur qui rame l'allonge. Sans rotation, en revanche, l'ordinateur est figé, et l'attente avec lui : elle reprend quand l'arbre tourne de nouveau.

Les évènements qui arrivent pendant l'attente ne sont pas perdus. Ils patientent dans la file (256 au plus, le plus ancien part quand elle est pleine) jusqu'au prochain os.pull_event ou read :

Brass
os.queue_event("remplir")
sleep(1)
local e = os.pull_event()
print(e.name .. " était encore là")
Écran
remplir était encore là

Pour attendre « un signal de redstone ou 5 secondes, le premier des deux », sleep ne suffit pas : lancez une minuterie avec os.start_timer et attendez avec os.pull_event (voir Temps et minuteries).

Une durée qui n'est pas un nombre arrête le programme : bad argument #1 to 'sleep' (number expected, got string).

Voir aussi os.start_timer() os.pull_event() os.clock()

Valeurs et conversions

#

tostring(value)

→ string

Convertit une valeur en texte.

Paramètres
value any
n'importe quelle valeur
Renvoie
string
la valeur sous forme de texte

.. transforme déjà les nombres en texte tout seul, mais pas true, false ni nil : "alimenté : " .. true arrête le programme avec attempt to concatenate a boolean value. C'est là qu'il faut tostring.

valeurtexte
42, -7, 2.5"42", "-7", "2.5"
true, false, nil"true", "false", "nil"
une table"table: 0x1b6d3586" (le nombre change d'une table à l'autre)
une de vos fonctions, un itérateur"function: 0x..."
une fonction intégrée comme print"builtin: 0x..."

Les nombres s'écrivent de la même façon partout (avec print, .., tostring, table.concat) : les entiers sous 1015 sans virgule, les autres avec au plus 14 chiffres significatifs, en notation exponentielle quand ils sont très grands ou très petits. Le séparateur décimal est toujours un point. La division par zéro donne inf ou -inf, et 0 / 0 donne nan (« pas un nombre »).

Brass
print(tostring(64) .. " objets")
print(0.1 + 0.2, 1 / 3)
print(123456789 * 1000000000, 0.00001)
print(1 / 0, -1 / 0, 0 / 0)
local alimente = true
print("alimenté : " .. tostring(alimente))
Écran
64 objets
0.3 0.33333333333333
1.23456789e+17  1e-05
inf -inf    nan
alimenté : true

Pour un nombre fixe de décimales (3.10) ou des colonnes alignées, utilisez plutôt string.format.

Voir aussi tonumber() string.format()

#

tonumber(value [, base])

→ number|nil

Convertit un texte en nombre, ou renvoie nil.

Paramètres
value string|number
le texte à convertir (un nombre revient tel quel)
base number facultatif
la base des chiffres, un entier de 2 à 36
Renvoie
number|nil
le nombre, ou nil quand le texte n'est pas un nombre

Ce que renvoie read, ce que contient un fichier et ce que le shell passe dans arg est toujours du texte, et calculer avec du texte est une erreur ("10" + 1 s'arrête sur attempt to perform arithmetic on a string value). tonumber en fait un nombre, ou donne nil si le texte n'en est pas un, ce qui permet de vérifier.

Acceptés : des espaces autour, un signe, des décimales avec un point, un exposant, l'hexadécimal avec 0x. Refusés (nil) : tout autre caractère, un texte vide, une virgule décimale ("1,5", attention aux habitudes françaises), des mots. Les valeurs qui ne sont ni du texte ni des nombres (nil, true, une table) donnent aussi nil.

Brass
print(tonumber("42") + 1)
print(tonumber(" -3.5 "))
print(tonumber("1e3"), tonumber("0x1F"))
print(tonumber("12 pommes"), tonumber("1,5"))
Écran
43
-3.5
1000    31
nil nil

Avec une base, le texte contient un entier écrit dans cette base, avec les lettres a à z (ou A à Z) pour les chiffres 10 à 35. Pas de préfixe 0x ni de décimales sous cette forme. La valeur doit alors être un texte (tonumber(42, 16) s'arrête sur bad argument #1 to 'tonumber' (string expected, got number)), et une base hors de 2 à 36 s'arrête sur bad argument #2 to 'tonumber' (base out of range).

Brass
print(tonumber("ff", 16), tonumber("1011", 2), tonumber("Z", 36))
print(tonumber("12", 2))
Écran
255 11  35
nil

Demander un nombre au joueur. Reposez la question tant que la réponse n'est pas valable. Comme read donne du texte, tonumber vérifie que c'est un nombre, puis le programme vérifie qu'il est dans les bornes :

Brass
local function demander_nombre(question, bas, haut)
  while true do
    write(question .. " (" .. bas .. " à " .. haut .. ") : ")
    local n = tonumber(read())
    if n == nil then
      print("Ce n'est pas un nombre.")
    elseif n ~= math.floor(n) or n < bas or n > haut then
      print("Un nombre entier de " .. bas .. " à " .. haut .. ", s'il vous plaît.")
    else
      return n
    end
  end
end

local vitesse = demander_nombre("Vitesse visée en tr/min", 0, 256)
print("Vitesse réglée à " .. vitesse .. " tr/min")

tonumber(x) or 0 est un raccourci courant quand une valeur absente ou abîmée peut simplement compter pour zéro.

Voir aussi tostring() read()

#

type(value)

→ string

Donne le type d'une valeur : "nil", "number", "string", "boolean", "table" ou "function".

Paramètres
value any
n'importe quelle valeur, nil compris
Renvoie
string
"nil", "number", "string", "boolean", "table" ou "function"

Servez-vous-en quand une valeur peut être de plusieurs sortes : des données reçues avec net.receive (du texte ou une table), le résultat d'une méthode d'appareil, un paramètre facultatif. Vos fonctions, les fonctions intégrées (print) et les itérateurs de pairs et ipairs sont tous des "function".

Brass
print(type(64), type("iron"), type(true))
print(type(nil), type({}), type(print))
Écran
number  string  boolean
nil table   function
Brass
local function decrire(donnees)
  if type(donnees) == "table" then
    return "une table de " .. #table.keys(donnees) .. " entrées"
  elseif type(donnees) == "number" then
    return "le nombre " .. donnees
  end
  return tostring(donnees)
end
print(decrire({x = 10, z = -4}))
print(decrire(15))
print(decrire("bonjour"))
Écran
une table de 2 entrées
le nombre 15
bonjour

type() sans aucun argument arrête le programme avec bad argument #1 to 'type' (value expected) ; type(nil) est permis et donne "nil".

Voir aussi tostring()

Boucles

Une boucle for ... in a besoin de quelque chose à parcourir. pairs(t) visite toutes les clés d'une table, ipairs(t) la partie liste dans l'ordre. Une liste se parcourt aussi sans elles : for v in t do donne les valeurs t[1], t[2]... (voir Tables). Mais for k, v in t do, avec deux variables et sans pairs, s'arrête sur use pairs() or ipairs() to iterate a table with two variables.

#

pairs(t)

→ function

Parcourt toutes les clés et valeurs d'une table : for k, v in pairs(t) do.

Paramètres
t table
la table à parcourir
Renvoie
function
un itérateur, pour une boucle for

L'ordre est toujours le même : d'abord les entiers 1, 2, 3... dans l'ordre, puis les autres clés dans l'ordre où elles ont été ajoutées. Contrairement à Lua, où l'ordre est aléatoire, vous pouvez compter dessus, par exemple pour afficher une table dans l'ordre où vous l'avez écrite. Les clés dont la valeur est nil n'existent pas et ne sont pas visitées.

Brass
local stock = {fer = 1200, cuivre = 640, zinc = 96}
stock.laiton = 32
for objet, nombre in pairs(stock) do
  print(objet, nombre)
end
Écran
fer 1200
cuivre  640
zinc    96
laiton  32
Brass
local t = {"premier", "deuxième", mode = "auto"}
t[3] = "troisième"
for k, v in pairs(t) do
  print(k, v)
end
Écran
1   premier
2   deuxième
3   troisième
mode    auto

Modifier la table pendant la boucle. Changer la valeur d'une clé, ou retirer une clé (la mettre à nil), ne pose aucun problème : une clé retirée qui n'a pas encore été visitée est sautée. Voici comment vider une table de ses tâches finies :

Brass
local taches = {presse = "finie", mixeur = "en cours", scie = "finie"}
for nom, etat in pairs(taches) do
  if etat == "finie" then
    taches[nom] = nil
  end
end
print(#table.keys(taches) .. " tâche restante")
Écran
1 tâche restante

Ajouter des clés pendant la boucle, c'est autre chose : certaines seront peut-être visitées, d'autres non, et ajouter au bout de la liste qu'on parcourt peut rendre la boucle infinie. Rangez ce qu'il faut ajouter dans une autre table, et ajoutez-le après la boucle.

La valeur que renvoie pairs est un itérateur : il ne marche que dans une boucle for. L'appeler vous-même s'arrête sur an iterator can only be used in a 'for' loop. Une valeur qui n'est pas une table arrête le programme : pairs(nil) donne bad argument #1 to 'pairs' (table expected, got no value), en général parce qu'une fonction a renvoyé nil là où vous attendiez une table.

Voir aussi ipairs() table.keys()

#

ipairs(t)

→ function

Parcourt t[1], t[2]... jusqu'au premier nil.

Paramètres
t table
la liste à parcourir
Renvoie
function
un itérateur, pour une boucle for

Il donne la position et la valeur, dans l'ordre, et ignore les clés nommées. Utilisez-le pour les listes : gares d'une ligne, emplacements, étapes d'une recette.

Brass
local arrets = {"Mine", "Fonderie", "Dépôt"}
arrets.ligne = "rouge"
for i, nom in ipairs(arrets) do
  print(i .. ". " .. nom)
end
Écran
1. Mine
2. Fonderie
3. Dépôt

Il s'arrête à la première position vide, même si des valeurs suivent. pairs les montrerait :

Brass
local cases = {"charbon", "fer", "or"}
cases[2] = nil
for i, objet in ipairs(cases) do print("ipairs", i, objet) end
for i, objet in pairs(cases) do print("pairs", i, objet) end
Écran
ipairs  1   charbon
pairs   1   charbon
pairs   3   or

La boucle lit t[i] à chaque tour : les changements faits pendant la boucle sont vus. Retirer des éléments avec table.remove en avançant saute l'élément qui glisse à la place retirée : parcourez plutôt la liste à l'envers avec un for numérique (voir table.remove).

Voir aussi pairs() table.insert()

Erreurs

Quand quelque chose tourne mal, le programme s'arrête et l'écran affiche en rouge le fichier, la ligne et le message : startup:12: attempt to index a nil value (local 'coffre'). Dans les exemples de cette page, le programme s'appelle snippet. Le guide Erreurs et débogage explique comment lire ces messages, et Messages d'erreur les liste.

#

error(message)

Arrête le programme avec une erreur.

Paramètres
message any
le message (toute valeur est convertie en texte)

Utilisez-la quand le programme tombe sur une situation qu'il ne sait pas gérer : un appareil absent, un réglage hors limites. Le message reçoit le fichier et la ligne devant lui, le programme s'arrête et le shell revient, sauf si un pcall autour de l'appel attrape l'erreur.

Brass
local function regler_vitesse(rpm)
  if rpm < -256 or rpm > 256 then
    error("vitesse hors limites : " .. rpm)
  end
  print("vitesse réglée à " .. rpm)
end
regler_vitesse(128)
regler_vitesse(300)
print("jamais affiché")
Écran
vitesse réglée à 128
snippet:3: vitesse hors limites : 300

Le message est toujours converti en texte avec tostring : error(42) donne 42, error() donne nil, et une table devient table: 0x... : une erreur ne peut donc pas transporter une table jusqu'au pcall qui l'attrape. Mettez les détails dans le texte. Il n'y a pas d'argument « niveau » comme en Lua : la position est toujours la ligne de l'appel à error, et un second argument est ignoré.

Voir aussi pcall() assert()

#

pcall(f, ...)

→ table

Appelle une fonction et attrape ses erreurs : vous obtenez {ok=true, value=...} ou {ok=false, error="..."}.

Paramètres
f function
la fonction à appeler
arguments any facultatif
des valeurs passées à f
Renvoie
table
{ok = true, value = ...} ou {ok = false, error = "..."}

pcall(f, a, b) appelle f(a, b). Si elle va jusqu'au bout, vous obtenez une table avec ok = true et value, ce que f a renvoyé (absent si f n'a rien renvoyé). Si une erreur se produit n'importe où dedans, le programme ne s'arrête pas : vous obtenez ok = false et error, le message sous forme de texte, fichier et ligne compris. Comme une fonction Brass ne renvoie qu'une valeur, pcall donne une table là où Lua donne deux valeurs.

Brass
local function diviser(a, b)
  if b == 0 then
    error("division par zéro")
  end
  return a / b
end
local r = pcall(diviser, 10, 4)
print(r.ok, r.value)
r = pcall(diviser, 1, 0)
print(r.ok, r.error)
Écran
true    2.5
false   snippet:3: division par zéro

Ce qu'il attrape : toutes les erreurs levées pendant que f tourne, dans son propre code et dans tout ce qu'elle appelle. error et assert, un mauvais argument donné à une fonction de bibliothèque, un calcul sur nil, un appareil qui a été cassé, un disque plein, out of memory, stack overflow, un import qui échoue. Il marche aussi autour de fonctions qui attendent : f peut appeler sleep, read ou os.pull_event.

Ce qu'il ne peut pas attraper : Ctrl+T et le bouton Stopper, qui arrêtent toujours le programme, ni os.reboot et os.shutdown. Une faute de syntaxe dans le programme lui-même ne s'attrape pas non plus : le programme ne démarre jamais.

pcall sur autre chose qu'une fonction n'arrête pas le programme : pcall(nil) donne {ok = false, error = "attempt to call a nil value"}, sans numéro de ligne. Pratique pour appeler une méthode qu'un appareil n'a peut-être pas.

Charger un fichier de réglages sans risque. Les réglages d'une ligne de presses sont dans un fichier que le joueur peut modifier. S'il manque ou s'il est faux, le programme garde ses réglages par défaut au lieu de planter :

Brass
fs.write("presse.cfg", "vitesse = rapide\ncote = back\n")   -- une faute dans le fichier

local function charger_config(chemin)
  local texte = fs.read(chemin)
  if texte == nil then
    error("pas de fichier " .. chemin)
  end
  local config = {}
  for _, ligne in ipairs(texte:split("\n")) do
    local egal = ligne:find("=")
    if egal then
      local cle = ligne:sub(1, egal - 1):trim()
      local valeur = ligne:sub(egal + 1):trim()
      config[cle] = tonumber(valeur) or valeur
    end
  end
  if type(config.vitesse) ~= "number" then
    error("vitesse invalide : '" .. tostring(config.vitesse) .. "'")
  end
  return config
end

local config = {vitesse = 32, cote = "back"}   -- si le fichier est mauvais
local r = pcall(charger_config, "presse.cfg")
if r.ok then
  config = r.value
else
  print("presse.cfg ignoré :")
  print(r.error)
end
print("Presse à " .. config.vitesse .. " tr/min, " .. config.cote)
Écran
presse.cfg ignoré :
snippet:18: vitesse invalide : 'rapide'
Presse à 32 tr/min, back
Astuce

Utilisez pcall autour de ce qui peut échouer pour des raisons extérieures à votre programme : un bloc qu'on peut casser, un fichier que les joueurs modifient, un message venu d'un autre ordinateur. N'enveloppez pas tout : une erreur cachée est un bug que vous ne verrez pas.

Voir aussi error() assert()

#

assert(value [, message])

→ any

Arrête le programme avec une erreur si la valeur est false ou nil ; sinon la renvoie.

Paramètres
value any
la valeur à vérifier
message any facultatif
le message d'erreur, "assertion failed!" s'il est omis
Renvoie
any
value elle-même, quand elle n'est ni false ni nil

assert(condition, "message") est un if not condition then error("message") end en une ligne. Comme il renvoie la valeur vérifiée, il peut vérifier et garder une valeur d'un seul coup : local texte = assert(fs.read("recettes"), "pas de fichier de recettes").

Brass
local coffre = {size = 27}
local cases = assert(coffre.size, "le coffre n'a pas de taille")
print("emplacements : " .. cases)
assert(cases > 30, "trop petit : " .. cases .. " emplacements")
Écran
emplacements : 27
snippet:4: trop petit : 27 emplacements
Attention

Le message est construit avant qu'assert ne s'exécute, même quand tout va bien. assert(nombre, "mauvais nombre : " .. nombre) échoue sur le .. quand nombre vaut nil, avec attempt to concatenate a nil value au lieu de votre message.

Voir aussi error() pcall()

Autres fichiers

Un programme peut se découper en plusieurs fichiers : une bibliothèque d'outils partagée par plusieurs programmes, les réglages d'une machine, le code de dessin d'un tableau de commande. Le guide Programmes en plusieurs fichiers montre comment les organiser, et Un projet en plusieurs fichiers en est un exemple complet.

#

import(path)

→ any⚙ coût 1 par 16 caractères du fichier

Exécute un autre fichier une seule fois et renvoie ce qu'il renvoie : import "lib/outils".

Paramètres
path string
le fichier à exécuter, relatif au dossier du fichier qui importe, ou depuis la racine avec un / au début
Renvoie
any
ce que le fichier renvoie avec return, nil s'il ne renvoie rien

Le fichier importé est un programme Brass ordinaire. Le plus souvent, il se termine par return { ... }, une table de fonctions :

lib/stock
-- outils partagés par les programmes de l'entrepôt
local function piles(nombre)
  return math.ceil(nombre / 64)
end

local function barre(nombre, capacite, largeur)
  local plein = math.min(largeur, math.floor(nombre / capacite * largeur))
  return "[" .. string.rep("#", plein) .. string.rep(".", largeur - plein) .. "]"
end

return {piles = piles, barre = barre}
startup
local stock = import "lib/stock"
print(stock.piles(1000) .. " piles")
print(stock.barre(1000, 2048, 20))

Avec un texte entre guillemets, les parenthèses sont facultatives : import "lib/stock" vaut import("lib/stock").

Où le fichier est cherché. Par rapport au dossier du fichier qui appelle import, pas au dossier courant du shell : jeux/serpent/main qui appelle import "dessin" charge jeux/serpent/dessin. .. remonte d'un dossier et un / au début part de la racine : import "../lib/stock", import "/lib/stock". À l'invite brass, le chemin est relatif au dossier courant. Le nom est pris tel quel : aucune extension n'est ajoutée, import "outils" charge le fichier outils, pas outils.lua.

Une fois par programme. Le premier import d'un fichier l'exécute. Tous les import suivants du même fichier, depuis n'importe quel fichier du programme, rendent la même valeur sans le relancer. Tous les fichiers partagent donc un seul exemplaire d'une bibliothèque, avec ses variables. Le lancement suivant du programme repart de zéro. Un fichier dont l'exécution s'est arrêtée sur une erreur n'est pas retenu : l'importer à nouveau le relance.

Ce qui est partagé. Les globales (fonctions et variables définies sans local) sont vues par tous les fichiers du programme. Les local du niveau du fichier lui restent privées. Préférez les locales et une table renvoyée : deux bibliothèques ne peuvent alors pas écraser les noms l'une de l'autre.

Cet exemple écrit une petite bibliothèque avec fs.write, puis l'importe deux fois :

Brass
fs.write("lib/unites", "chargements = (chargements or 0) + 1\n"
  .. "local par_pile = 64\n"
  .. "return {piles = function(n) return math.ceil(n / par_pile) end}\n")
local unites = import "lib/unites"
local encore = import "lib/unites"
print(unites.piles(200), chargements, unites == encore, par_pile)
Écran
4   1   true    nil

unites.piles marche, le fichier n'a tourné qu'une fois (chargements vaut 1, c'est une globale), les deux imports ont donné la même table, et la locale par_pile est restée dans la bibliothèque.

Quand il échoue, import arrête le programme avec une erreur que pcall peut attraper :

messagecause
cannot import 'nom': no such fileaucun fichier à ce chemin (vérifiez le dossier de référence)
cannot import 'nom': it is a folderle chemin désigne un dossier
lib/stock:4: ...une erreur dans le fichier importé, avec son propre nom et sa ligne
circular import: 'a' is still being importeda importe b, qui importe a à son tour : mettez le code commun dans un troisième fichier
no storage mediuml'ordinateur n'a pas de disque

Un fichier de réglages peut lui aussi être un fichier Brass, return {vitesse = 64, cote = "back"}, chargé avec une solution de repli :

Brass
local r = pcall(import, "/reglages")
local reglages = {vitesse = 32, cote = "back"}
if r.ok and type(r.value) == "table" then
  reglages = r.value
end

Voir aussi require() Programmes en plusieurs fichiers

#

require(path)

→ any

Comme import.

Paramètres
path string
le fichier à exécuter, exactement comme pour import
Renvoie
any
ce que le fichier renvoie

require est exactement la même fonction qu'import, pour les habitudes des joueurs de ComputerCraft et de Lua. Elle partage son cache : un fichier chargé avec l'une puis avec l'autre ne s'exécute qu'une fois. Contrairement à Lua, le chemin s'écrit avec des barres obliques, pas des points : require "lib/outils", pas require "lib.outils" (qui chercherait un fichier nommé lib.outils).

Brass
local outils = require "lib/outils"

Voir aussi import()

Arguments du programme

#

arg

→ tablevaleur

Les mots tapés après le nom du programme à l'invite, sous forme de liste de textes.

Renvoie
table
les mots tapés après le nom du programme, arg[0] étant le fichier du programme

Taper impulsion back 3 à l'invite (ou run impulsion back 3) lance le fichier impulsion avec arg[1] = "back" et arg[2] = "3". #arg est le nombre de mots, et arg[0] le chemin du programme depuis la racine, sans la barre du début ("impulsion", ou "outils/impulsion" pour un programme rangé dans un dossier).

impulsion
-- usage : impulsion <côté> [fois]
local cote = arg[1]
local fois = tonumber(arg[2]) or 1
if cote == nil then
  print("Usage : " .. arg[0] .. " <côté> [fois]")
  return
end
print("Impulsions sur " .. cote .. " : " .. fois .. " fois")
for i = 1, fois do
  rs.set(cote, true)
  sleep(0.5)
  rs.set(cote, false)
  sleep(0.5)
end
Terminal
> impulsion
Usage : impulsion <côté> [fois]
> impulsion back 3
Impulsions sur back : 3 fois
  • Les mots sont coupés aux espaces, et les guillemets n'ont rien de spécial : stock "Lingot de fer" donne trois mots, "Lingot, de et fer".
  • Ce sont toujours des textes : convertissez les nombres avec tonumber.
  • Un programme lancé au démarrage (startup) ne reçoit aucun mot : arg vaut {}, avec arg[0] = "startup".
  • arg existe pendant qu'un programme lancé depuis un fichier tourne. À l'invite brass, il vaut nil.
  • C'est une globale ordinaire : tous les fichiers du programme la voient, et le programme peut la modifier.