Create: Computing AgesDoc Brass
Bibliothèques

string

Le texte : découper, chercher, changer la casse, mettre en forme des nombres, couper en morceaux et nettoyer. Tout texte a aussi ces fonctions comme méthodes : s:upper().

Tous les ordinateurs

La bibliothèque string travaille sur le texte : noms d'objets, lignes tapées par un joueur, lignes d'un fichier, messages d'un autre ordinateur. Chaque fonction renvoie un nouveau texte : un texte, lui, ne change jamais.

Écriture en méthode. Toute valeur texte possède aussi ces fonctions comme méthodes : nom:upper() vaut string.upper(nom), et ligne:sub(1, 3) vaut string.sub(ligne, 1, 3). Ça marche sur toute expression qui donne un texte, read():trim() compris. Un texte écrit directement dans le code demande des parenthèses : ("fer"):upper(), car "fer":upper() ne compile pas.

Brass
local objet = "minecraft:iron_ingot"
local deux_points = objet:find(":")
print(objet:sub(deux_points + 1))
print(#objet, objet:upper())
print(("laiton"):rep(2, "+"))
Écran
iron_ingot
20  MINECRAFT:IRON_INGOT
laiton+laiton

Les positions commencent à 1 pour le premier caractère. Une position négative compte depuis la fin : -1 est le dernier caractère, -2 celui d'avant. Chaque caractère compte pour un, lettres accentuées comprises. Un texte ne s'indexe pas comme une table : s[1] s'arrête sur attempt to index a string value with a number key, utilisez s:sub(1, 1).

Opérateurs. .. colle deux textes et transforme les nombres en texte tout seul ("x" .. 3 donne "x3"), mais refuse nil, true et les tables (attempt to concatenate a nil value) : passez par tostring. #s est la longueur. == compare les caractères, et < compare les textes selon les codes des caractères : "Zinc" < "apple" (les majuscules passent avant) et "10" < "9" (chiffre par chiffre). Le calcul ne transforme jamais un texte en nombre : "10" + 1 est une erreur, utilisez tonumber.

Écrire un texte. Entre "..." ou '...', avec les séquences \n (nouvelle ligne), \t (tabulation), \\, \", \', \r, \0 et \xNN (un caractère par son code hexadécimal). Un long texte peut s'étaler sur plusieurs lignes entre [[ et ]], où rien n'est interprété.

Limites

Un texte contient au plus 65536 caractères. Les dépasser, avec .., string.rep, string.format ou table.concat, arrête le programme avec string too long. Toutes les fonctions de cette page qui prennent un texte acceptent aussi un nombre, converti comme le fait tostring : string.len(1234) vaut 4.

Fonctions
string.len(s)Le nombre de caractères d'un texte.
string.upper(s)Met le texte en majuscules.
string.lower(s)Met le texte en minuscules.
string.sub(s, i [, j])La partie du texte de la position i à la position j, toutes deux comprises.
string.find(s, text [, init])La position d'un texte dans un autre, ou nil.
string.starts(s, prefix)Vrai si le texte commence par prefix.
string.ends(s, suffix)Vrai si le texte finit par suffix.
string.rep(s, n [, sep])Le texte répété n fois, avec un séparateur facultatif entre les copies.
string.reverse(s)Le texte à l'envers.
string.format(fmt, ...)Construit un texte à partir d'un modèle et de valeurs : "%d objets", "%.2f", "%5s"...
string.split(s [, sep])Coupe le texte en une liste de morceaux, à chaque séparateur.
string.trim(s)Enlève les espaces, tabulations et retours à la ligne aux deux bouts du texte.
string.byte(s [, i])Le code d'un caractère du texte (le premier par défaut).
string.char(...)Un texte à partir de codes de caractères.

Longueur et casse

#

string.len(s)

→ number

Le nombre de caractères d'un texte.

Paramètres
s string
un texte
Renvoie
number
son nombre de caractères

#s donne la même chose et s'écrit plus vite. Un usage courant : centrer un titre à l'écran.

Brass
print(string.len("Andésite"), #"Andésite", #"")
local titre = "FERME À FER"
local largeur = term.get_size().w
print(string.rep(" ", math.floor((largeur - #titre) / 2)) .. titre)
Écran
8   8   0
                    FERME À FER

Voir aussi string.sub()

#

string.upper(s)

→ string

Met le texte en majuscules.

Paramètres
s string
un texte
Renvoie
string
le même texte en majuscules

Les lettres accentuées suivent : é devient É. Les autres caractères ne bougent pas.

Brass
local nom = "boitier de laiton"
print(nom:upper())
print(string.upper("Portail 3 : ouvert"))
print(("dépôt"):upper())
Écran
BOITIER DE LAITON
PORTAIL 3 : OUVERT
DÉPÔT

Voir aussi string.lower()

#

string.lower(s)

→ string

Met le texte en minuscules.

Paramètres
s string
un texte
Renvoie
string
le même texte en minuscules

La comparaison de textes est exacte : "Oui" ~= "oui". Passez ce qu'un joueur a tapé en minuscules avant de le comparer, pour que OUI, Oui et oui marchent tous :

Brass
local function est_oui(reponse)
  local r = reponse:trim():lower()
  return r == "oui" or r == "o"
end
print(est_oui("OUI"), est_oui("  o "), est_oui("non"))
Écran
true    true    false

Dans un programme : write("Ouvrir le portail ? ") if est_oui(read()) then ... end.

Voir aussi string.upper() string.trim()

Découper et chercher

#

string.sub(s, i [, j])

→ string

La partie du texte de la position i à la position j, toutes deux comprises.

Paramètres
s string
un texte
i number
la première position gardée (négative : depuis la fin)
j number facultatif
la dernière position gardée, comprise (-1, la fin, si elle est omise)
Renvoie
string
les caractères de i à j
Brass
local s = "Presse mécanique"
print(s:sub(1, 6))
print(s:sub(8))
print(s:sub(-4))
print(s:sub(-4, -3))
Écran
Presse
mécanique
ique
iq

Elle n'échoue jamais sur des positions hors du texte : un début avant le premier caractère part du premier, une fin après le dernier caractère s'arrête au dernier, et un début après la fin donne "". Les positions doivent être des entiers (bad argument #2 to 'sub' (number has no integer representation)).

Brass
local s = "Scie"
print("[" .. s:sub(0, 2) .. "]", "[" .. s:sub(3, 99) .. "]", "[" .. s:sub(6) .. "]")
Écran
[Sc]    [ie]    []

Pour faire tenir un nom dans une colonne, coupez-le et marquez la coupure :

Brass
local function ajuster(nom, largeur)
  if #nom > largeur then
    return nom:sub(1, largeur - 1) .. "~"
  end
  return nom
end
print(ajuster("Alliage d'andésite", 10))
print(ajuster("Zinc", 10))
Écran
Alliage d~
Zinc

Voir aussi string.find() string.split()

#

string.find(s, text [, init])

→ number|nil⚙ coût 1 par 16 caractères

La position d'un texte dans un autre, ou nil.

Paramètres
s string
le texte où chercher
text string
le texte cherché, pris tel quel
init number facultatif
la position où commence la recherche (négative : depuis la fin)
Renvoie
number|nil
la position où text apparaît pour la première fois, ou nil

La recherche est simple : chaque caractère se représente lui-même. Les motifs de Lua n'existent pas en Brass, donc ., %, [ ou ( n'ont rien de spécial (en Lua, ("v1.2"):find(".") trouve n'importe quel caractère, ici il trouve le point). Il n'y a pas non plus de string.match, gmatch ni gsub : find, sub et split font le même travail. Un quatrième argument (le drapeau « plain » de Lua) est accepté et ignoré.

Seule la position de début revient. La correspondance se termine en debut + #text - 1. La recherche distingue majuscules et minuscules : passez les deux textes en minuscules pour l'ignorer.

Brass
print(("v1.2"):find("."))
print(("Lingot de fer"):find("fer"), ("Lingot de fer"):find("Fer"))
local id = "create:brass_ingot"
local deux_points = id:find(":")
print(id:sub(1, deux_points - 1), id:sub(deux_points + 1))
Écran
3
11  nil
create  brass_ingot

init fait partir la recherche plus loin, ce qui permet de trouver toutes les occurrences. Un départ après la fin donne nil :

Brass
local journal = "bourrage presse; ok; bourrage scie; ok"
local nombre, depuis = 0, 1
while true do
  local pos = journal:find("bourrage", depuis)
  if pos == nil then
    break
  end
  nombre = nombre + 1
  depuis = pos + 1
end
print("bourrages : " .. nombre)
Écran
bourrages : 2

Voir aussi string.sub() string.starts() string.split()

#

string.starts(s, prefix)

→ boolean

Vrai si le texte commence par prefix.

Paramètres
s string
un texte
prefix string
le début cherché
Renvoie
boolean
true si s commence par prefix

Une extension de Brass (Lua n'en a pas). Exacte, sensible à la casse, et un préfixe vide est toujours trouvé. Pratique pour trier des identifiants d'objets par mod, ou des commandes par leur premier mot :

Brass
local objets = {"create:brass_ingot", "minecraft:iron_ingot", "create:zinc_ingot"}
for _, id in ipairs(objets) do
  if id:starts("create:") then
    print(id)
  end
end
Écran
create:brass_ingot
create:zinc_ingot

Voir aussi string.ends() string.find()

#

string.ends(s, suffix)

→ boolean

Vrai si le texte finit par suffix.

Paramètres
s string
un texte
suffix string
la fin cherchée
Renvoie
boolean
true si s finit par suffix

Une extension de Brass, comme string.starts. Utilisez-la pour des familles d'objets ou des noms de fichiers :

Brass
local ids = {"minecraft:iron_ingot", "minecraft:iron_nugget", "create:zinc_ingot"}
local lingots = 0
for _, id in ipairs(ids) do
  if id:ends("_ingot") then
    lingots = lingots + 1
  end
end
print(lingots .. " sortes de lingots")
print(("rapport.log"):ends(".log"), ("rapport.log"):ends(""))
Écran
2 sortes de lingots
true    true

Voir aussi string.starts()

Construire du texte

#

string.rep(s, n [, sep])

→ string⚙ coût 1 par 8 copies

Le texte répété n fois, avec un séparateur facultatif entre les copies.

Paramètres
s string
le texte à répéter
n number
combien de fois (0 ou moins donne "")
sep string facultatif
un texte placé entre les copies
Renvoie
string
les copies, mises bout à bout
Brass
print(string.rep("=", 20))
print(("ab"):rep(3, ", "))
print("[" .. ("x"):rep(0) .. "]")
Écran
====================
ab, ab, ab
[]

n doit être un entier. Un résultat de plus de 65536 caractères s'arrête sur string too long.

Une barre de progression en caractères : la partie pleine, puis la partie vide, toujours de la même largeur.

Brass
local function barre(valeur, max, largeur)
  local plein = math.min(largeur, math.round(valeur / max * largeur))
  return "[" .. string.rep("#", plein) .. string.rep("-", largeur - plein) .. "]"
end
print(barre(750, 1000, 20) .. " 75%")
print(barre(0, 1000, 20))
print(barre(1200, 1000, 20) .. " plein")
Écran
[###############-----] 75%
[--------------------]
[####################] plein

Voir aussi string.format()

#

string.reverse(s)

→ string

Le texte à l'envers.

Paramètres
s string
un texte
Renvoie
string
les mêmes caractères, le dernier en premier

Une ligne de train notée avec une lettre par gare se parcourt à l'envers au retour :

Brass
local trajet = "MFUD"   -- Mine, Fonderie, Usine, Dépôt
print("Aller :  " .. trajet)
print("Retour : " .. trajet:reverse())
Écran
Aller :  MFUD
Retour : DUFM

Voir aussi string.sub()

#

string.format(fmt, ...)

→ string

Construit un texte à partir d'un modèle et de valeurs : "%d objets", "%.2f", "%5s"...

Paramètres
fmt string
le modèle, avec un code % à la place de chaque valeur
values any facultatif
une valeur par code, dans l'ordre
Renvoie
string
le modèle avec les valeurs écrites dedans

Chaque code % du modèle est remplacé par la valeur suivante, écrite comme le code le demande :

codeprendécritexemple
%d ou %iun entierle nombre42
%fun nombreavec 6 décimales, ou .N décimales3.141593, %.2f → 3.14
%sn'importe quelle valeurla valeur, comme tostringFer, true, nil
%x, %Xun entieren hexadécimalff, FF
%%rienun signe %%

Entre le % et la lettre, dans cet ordre, vous pouvez ajouter :

  • des drapeaux : - complète à droite (la valeur se cale à gauche), 0 complète les nombres avec des zéros, + affiche toujours le signe d'un nombre, une espace met une espace là où un nombre positif n'a pas de signe.
  • une largeur, d'un ou deux chiffres : le nombre minimum de caractères, complété par des espaces à gauche. %5d écrit 42 sous la forme 42.
  • une précision, un point et des chiffres : le nombre de décimales pour %f, le nombre maximum de caractères gardés pour %s (%.3s coupe Andésite en And). %d et %x l'ignorent.

Le séparateur décimal est toujours un point, jamais une virgule.

Brass
print(string.format("%d objets dans %d coffres", 1250, 20))
print(string.format("Vitesse : %.1f tr/min", 63.27))
print(string.format("[%5d] [%-5d] [%05d]", 42, 42, 42))
print(string.format("%+d / %+d", 5, -5))
print(string.format("%x %X 100%%", 255, 3054))
print(("%.3s"):format("Andésite"))
Écran
1250 objets dans 20 coffres
Vitesse : 63.3 tr/min
[   42] [42   ] [00042]
+5 / -5
ff BEE 100%
And

Des colonnes. Une largeur par colonne, - pour les textes pour qu'ils s'alignent à gauche, rien pour les nombres pour qu'ils s'alignent à droite :

Brass
local stock = {
  {nom = "Lingot de fer", nombre = 1250},
  {nom = "Lingot de cuivre", nombre = 87},
  {nom = "Pépite de zinc", nombre = 4},
  {nom = "Lingot de laiton", nombre = 320},
}
print(string.format("%-16s %6s", "Objet", "Nombre"))
print(string.rep("-", 23))
for _, ligne in ipairs(stock) do
  print(string.format("%-16s %6d", ligne.nom, ligne.nombre))
end
Écran
Objet            Nombre
-----------------------
Lingot de fer      1250
Lingot de cuivre     87
Pépite de zinc        4
Lingot de laiton    320

Une horloge. os.day_time() compte les ticks de la journée Minecraft, 0 correspondant à 6 h du matin. %02d écrit deux chiffres, avec un zéro devant si besoin :

Brass
local function horloge(ticks)
  local t = (ticks + 6000) % 24000
  local h = math.floor(t / 1000)
  local m = math.floor(t % 1000 * 60 / 1000)
  return string.format("%02d:%02d", h, m)
end
print(horloge(os.day_time()))
print(horloge(0), horloge(6500), horloge(18000))
local secondes = 754
print(string.format("encore %d:%02d", secondes // 60, secondes % 60))
Écran
12:00
06:00   12:30   00:00
encore 12:34

Quand elle échoue. string.format est stricte, et le programme s'arrête :

messagecause
bad argument #2 to 'format' (number has no integer representation)%d ou %x avec un nombre à virgule : arrondissez-le d'abord (math.floor, math.round)
bad argument #2 to 'format' (number expected, got string)%d, %f ou %x avec un texte, même "42" : convertissez-le avec tonumber
bad argument #3 to 'format' (no value)moins de valeurs que de codes
invalid conversion '%e' to 'format'un code qui n'existe pas en Brass (%c, %e, %g, %o, %q...), une largeur de trois chiffres, une précision au-delà de 99, un % seul à la fin (écrivez %%), ou des drapeaux qui ne vont pas avec le code : - ou 0 sans largeur, + avec %s ou %x

Les valeurs en trop sont ignorées. Avec %f, l'infini et « pas un nombre » s'écrivent Infinity et NaN (%s écrit inf et nan), et avec %x un nombre négatif sort sur 16 chiffres hexadécimaux (-1 donne ffffffffffffffff).

Voir aussi tostring() string.rep()

Couper et nettoyer

#

string.split(s [, sep])

→ table⚙ coût 1 par 16 caractères

Coupe le texte en une liste de morceaux, à chaque séparateur.

Paramètres
s string
le texte à couper
sep string facultatif
le séparateur, pris tel quel (une seule espace " " s'il est omis)
Renvoie
table
les morceaux, une liste de textes

Une extension de Brass qui remplace les motifs de Lua dans la plupart des cas. Le séparateur peut faire plusieurs caractères (", "). Les morceaux gardent tout ce qui se trouve entre deux séparateurs, donc :

  • deux séparateurs d'affilée donnent un morceau vide, et un séparateur au début ou à la fin donne un morceau vide à cet endroit ;
  • un texte sans le séparateur donne une liste avec le texte entier, et un texte vide donne {""} ;
  • un séparateur vide "" coupe entre chaque caractère.
Brass
local morceaux = ("fer,or,,cuivre"):split(",")
print(#morceaux, morceaux[3] == "", morceaux[4])
local lettres = ("ABC"):split("")
print(#lettres, lettres[1], lettres[3])
print(#("a b"):split(), #(""):split(","))
Écran
4   true    cuivre
3   A   C
2   1

Avec table.concat, il remplace un morceau de texte par un autre partout :

Brass
print(table.concat(("lingot_de_fer"):split("_"), " "))
Écran
lingot de fer

Lire les commandes tapées par le joueur. Sans séparateur, split coupe à chaque espace, donc deux espaces d'affilée donnent un mot vide : nettoyez la ligne et sautez les mots vides.

Brass
local function mots(ligne)
  local liste = {}
  for _, m in ipairs(ligne:trim():split(" ")) do
    if m ~= "" then
      table.insert(liste, m)
    end
  end
  return liste
end

local function executer(ligne)
  local m = mots(ligne)
  local commande = m[1]
  if commande == nil then
    return
  end
  if commande == "vitesse" then
    local rpm = tonumber(m[2])
    if rpm == nil then
      print("usage : vitesse <tr/min>")
    else
      print("vitesse réglée à " .. rpm .. " tr/min")
    end
  elseif commande == "ouvrir" or commande == "fermer" then
    print(commande .. " le portail " .. (m[2] or "1"))
  else
    print("commande inconnue : " .. commande)
  end
end

executer("vitesse   128")
executer("  ouvrir 3 ")
executer("vitesse rapide")
executer("")
executer("sauter")
Écran
vitesse réglée à 128 tr/min
ouvrir le portail 3
usage : vitesse <tr/min>
commande inconnue : sauter

Dans un vrai programme, les lignes viennent du clavier : while true do write("> ") executer(read()) end.

Voir aussi string.trim() table.concat()

#

string.trim(s)

→ string

Enlève les espaces, tabulations et retours à la ligne aux deux bouts du texte.

Paramètres
s string
un texte
Renvoie
string
le texte sans espaces à ses extrémités

Les espaces à l'intérieur restent. Nettoyez ce qu'un joueur a tapé et les lignes d'un fichier (un fichier écrit sur un autre système peut finir ses lignes par un \r invisible).

Brass
local tape = "   Lingot de fer  \n"
print("[" .. tape:trim() .. "]")
Écran
[Lingot de fer]

Voir aussi string.split()

Codes des caractères

Chaque caractère a un numéro, son code : A vaut 65, a 97, 0 48, é 233 (le code Unicode). Les codes servent à passer des lettres aux nombres et inversement.

#

string.byte(s [, i])

→ number|nil

Le code d'un caractère du texte (le premier par défaut).

Paramètres
s string
un texte
i number facultatif
la position du caractère (1 si elle est omise, négative : depuis la fin)
Renvoie
number|nil
le code de ce caractère, nil après la fin

Lua peut renvoyer plusieurs codes d'un coup. Brass en renvoie un : appelez-la une fois par position.

Brass
print(string.byte("A"), ("abc"):byte(2), ("abc"):byte(-1))
print(("abc"):byte(10))
Écran
65  98  99
nil

Les étagères d'un mur de stockage s'appellent A1, B4, C12 : la lettre donne la colonne.

Brass
local function case(code)
  local colonne = code:upper():byte(1) - string.byte("A") + 1
  local rangee = tonumber(code:sub(2))
  return {colonne = colonne, rangee = rangee}
end
local c = case("c12")
print(c.colonne, c.rangee)
Écran
3   12

Voir aussi string.char()

#

string.char(...)

→ string

Un texte à partir de codes de caractères.

Paramètres
codes number
un ou plusieurs codes de caractères, de 0 à 65535
Renvoie
string
le texte formé par ces caractères

string.char() sans code donne "". Un code hors de 0 à 65535 s'arrête sur bad argument #1 to 'char' (value out of range), un code à virgule sur bad argument #1 to 'char' (number has no integer representation) (le numéro est la position du code fautif). Les codes sous 32 sont des caractères de contrôle : l'écran les affiche comme ?.

Brass
print(string.char(72, 105))
for i = 1, 5 do
  write(string.char(64 + i) .. " ")
end
print()
print(string.char(("A"):byte() + 1))
Écran
Hi
A B C D E
B

Voir aussi string.byte()

Motifs courants

Un tableau de stock. string.format pour les colonnes, string.rep pour les barres, les couleurs de term :

Brass
local coffres = {
  {nom = "Lingot de fer", nombre = 3120, capacite = 4096},
  {nom = "Lingot de cuivre", nombre = 860, capacite = 4096},
  {nom = "Lingot de zinc", nombre = 3950, capacite = 4096},
  {nom = "Lingot de laiton", nombre = 140, capacite = 2048},
}
term.clear()
term.set_cursor(1, 1)
term.set_fg(term.colors.yellow)
print(string.format("%-16s %6s  %s", "OBJET", "NOMBRE", "NIVEAU"))
for _, c in ipairs(coffres) do
  local taux = c.nombre / c.capacite
  local plein = math.round(taux * 20)
  term.set_fg(term.colors.white)
  write(string.format("%-16s %6d  ", c.nom, c.nombre))
  if taux >= 0.9 then
    term.set_fg(term.colors.red)      -- presque plein : l'entrée va s'arrêter
  elseif taux < 0.1 then
    term.set_fg(term.colors.orange)   -- presque vide
  else
    term.set_fg(term.colors.lime)
  end
  print(string.rep("#", plein) .. string.rep(".", 20 - plein) .. string.format(" %3d%%", math.round(taux * 100)))
end
Écran
Écran

**Lire des lignes cle=valeur.** split coupe le fichier en lignes, find et sub coupent chaque ligne au =, trim nettoie les deux côtés, tonumber refait des nombres. L'exemple de pcall de Fonctions globales construit un chargeur de réglages complet sur cette base.

Brass
local texte = "vitesse = 64\ncote = back\n# un commentaire\n"
local config = {}
for _, ligne in ipairs(texte:split("\n")) do
  local egal = ligne:find("=")
  if egal and not ligne:starts("#") then
    config[ligne:sub(1, egal - 1):trim()] = ligne:sub(egal + 1):trim()
  end
end
print(config.vitesse, config.cote, tonumber(config.vitesse) * 2)
Écran
64  back    128