Bibliothèque string

La bibliothèque string fournit les opérations de manipulation de chaînes : sous-chaînes, casse, recherche/remplacement par motif (« pattern », façon Lua — pas des expressions régulières), formatage façon printf, et (dé)sérialisation binaire.

Toute valeur chaîne possède une métatable qui redirige l'indexation vers cette bibliothèque, ce qui permet la notation « méthode » en plus de la notation classique :

local s = "Hello, World!"
print(s:upper())          -- équivalent à string.upper(s)
print(string.len(s))

Sauf mention contraire, les positions (i, j, pos, ...) sont 1-based, comme partout ailleurs dans YScript. Une position négative compte depuis la fin de la chaîne (-1 = dernier caractère).

Vue d'ensemble

Fonction Description
string.byte(s [, i [, j]]) Codes numériques des caractères de s entre i et j
string.char(···) Construit une chaîne à partir de codes numériques
string.dump(function [, strip]) Sérialise une fonction en bytecode précompilé
string.find(s, pattern [, init [, plain]]) Cherche un motif, retourne les positions (et captures)
string.format(formatstring, ···) Formatage façon printf
string.fromencoding(s, encoding) Décode une chaîne « binaire » (octets) en texte selon encoding
string.gmatch(s, pattern [, init]) Itérateur sur toutes les occurrences d'un motif
string.gsub(s, pattern, repl [, n]) Remplace les occurrences d'un motif
string.len(s) Longueur de s
string.lower(s) Convertit en minuscules
string.match(s, pattern [, init]) Cherche un motif, retourne ses captures
string.netformat(formatstring, ···) Formatage façon .NET ("{0}", "{1:F2}", ...)
string.pack(fmt, ···) Sérialise des valeurs en chaîne binaire
string.packsize(fmt) Taille en octets qu'occuperait string.pack(fmt, ···)
string.rep(s, n [, sep]) Répète s n fois, séparées par sep
string.reverse(s) Inverse l'ordre des caractères
string.sub(s, i [, j]) Sous-chaîne de s entre i et j
string.toencoding(s, encoding) Encode un texte en chaîne « binaire » (octets) selon encoding
string.ucodepoint(s [, i [, j]]) Points de code Unicode des caractères de s entre i et j
string.ucodes(s) Itérateur position, point de code sur les caractères de s
string.ulen(s [, i [, j]]) Nombre de caractères de s (points de code, pas d'unités UTF-16)
string.uoffset(s, n [, i]) Position de début du n-ième caractère à partir de i
string.unpack(fmt, data [, pos]) Désérialise une chaîne binaire
string.upper(s) Convertit en majuscules

string.len(s)

Retourne la longueur de s, en caractères. Équivalent à l'opérateur #s.

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

Retourne la sous-chaîne de s allant de la position i à la position j (incluses). j vaut -1 par défaut (jusqu'à la fin de s). Les positions négatives comptent depuis la fin ; les positions hors bornes sont silencieusement ramenées dans [1, #s]. Si, après ajustement, i > j, retourne la chaîne vide.

print(("Hello"):sub(2, 4))   --> ell
print(("Hello"):sub(-3))     --> llo

string.upper(s) / string.lower(s)

Retournent s converti en majuscules / minuscules, selon la culture du script.

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

Retourne s répété n fois, chaque occurrence séparée par sep (chaîne vide par défaut). Si n <= 0, retourne la chaîne vide. Une erreur est levée si la chaîne résultante dépasserait la taille maximale représentable.

string.reverse(s)

Retourne s avec ses caractères dans l'ordre inverse.

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

Retourne, comme plusieurs valeurs, les codes numériques des caractères de s de la position i (défaut 1) à la position j (défaut i). Ne retourne aucune valeur si l'intervalle est vide après ajustement.

print(string.byte("A"))        --> 65
print(string.byte("ABC", 1, 3)) --> 65   66   67

string.char(···)

Inverse de string.byte : reçoit zéro ou plusieurs codes numériques et retourne la chaîne formée de ces caractères. Chaque code doit être compris entre 0 et 0x10FFFF (dernier point de code Unicode valide) :

print(string.char(72, 101, 108, 108, 111))   --> Hello
print(string.char(0x1F600))                  --> 😀

Unicode : caractères vs unités UTF-16

Les chaînes YScript sont du texte Unicode natif .NET (UTF-16) — voir Types et valeurs. La plupart des fonctions de cette bibliothèque (len, sub, byte, ...) raisonnent en unités de code UTF-16, pas en caractères Unicode : un caractère hors du plan de base (« BMP » — la plupart des emojis, certains sinogrammes rares, ...) occupe deux unités (une « paire de substitution »), donc #s compte 2 pour un seul emoji, et indexer au mauvais endroit avec string.sub/string.byte peut couper une paire en deux.

Les quatre fonctions suivantes raisonnent en points de code (un point de code = un caractère Unicode, quelle que soit sa largeur en UTF-16) — dans l'esprit de la bibliothèque utf8 de Lua, mais adaptées : YScript n'a pas besoin de décoder de l'UTF-8 stocké dans une chaîne d'octets puisque ses chaînes sont déjà du texte Unicode ; les positions qu'elles acceptent/retournent restent des indices UTF-16 (comme partout ailleurs dans cette bibliothèque), pas des offsets d'octets UTF-8.

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

Retourne, comme plusieurs valeurs, le point de code Unicode de chaque caractère de s dont le début se situe entre les positions i (défaut 1) et j (défaut i) — un point de code hors BMP compte pour une seule valeur de retour, même s'il occupe deux positions UTF-16.

local s = "a" .. string.char(0x1F600) .. "b"
print(string.ucodepoint(s, 1, #s))   --> 97   128512   98

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

Comme string.len, mais compte les caractères (points de code), pas les unités UTF-16 — j vaut -1 par défaut (jusqu'à la fin de s), comme string.sub. Retourne nil plus la position fautive si s contient une moitié de paire de substitution isolée, plutôt que de lever une erreur.

local s = "a" .. string.char(0x1F600) .. "b"
print(#s, string.ulen(s))   --> 4   3

string.uoffset(s, n [, i])

Retourne la position de début du n-ième caractère à partir de la position i : n = 0 retourne le début du caractère qui contient i (utile pour se replacer sur une frontière de caractère après une position calculée à la main) ; n > 0 avance de n - 1 caractères puis retourne cette position ; n < 0 recule de |n| caractères. i vaut 1 par défaut si n >= 0, #s + 1 sinon. Retourne nil si ce déplacement dépasse une extrémité de s.

local s = "a" .. string.char(0x1F600) .. "b"
print(string.uoffset(s, 2))       --> 2 (début de l'emoji)
print(string.uoffset(s, 0, 3))    --> 2 (position 3 tombe au milieu de l'emoji)

string.ucodes(s)

Itérateur utilisable dans un for générique, retournant à chaque étape la position (1-based, index UTF-16) et le point de code du caractère suivant de s — un passage par caractère, pas par unité UTF-16, donc une paire de substitution n'est visitée qu'une fois. Lève une erreur si s contient une moitié de paire de substitution isolée.

local s = "a" .. string.char(0x1F600) .. "b"
for p, c in string.ucodes(s) do
    print(p, c)
end
--> 1   97
--> 2   128512
--> 4   98

string.toencoding(s, encoding) / string.fromencoding(s, encoding)

Conversions entre un texte YScript normal et une représentation « binaire » où chaque caractère de la chaîne vaut un octet (0-255) — la même convention que le mode binaire de io et que string.pack/string.dump.

encoding accepte les mêmes noms/codepages que le 3ᵉ argument de io.open (y compris le pseudo-nom "binary").

string.find(s, pattern [, init [, plain]])

Cherche la première occurrence de pattern dans s, à partir de la position init (défaut 1). En cas de succès, retourne la position de début et de fin de l'occurrence, suivies des captures éventuelles du motif (voir « Motifs » plus bas). Retourne nil si aucune occurrence n'est trouvée.

Si plain est vrai, ou si pattern ne contient aucun caractère spécial de motif, la recherche se fait en texte brut (sous-chaîne exacte), sans interpréter pattern comme un motif — plus rapide et sans surprise pour une recherche littérale.

print(string.find("hello world", "wor"))       --> 7   9
print(string.find("hello world", "o", 6))       --> 8   8
print(string.find("2024-01-15", "(%d+)-(%d+)")) --> 1   7   2024   01

string.match(s, pattern [, init])

Comme string.find, mais retourne directement les captures de la première occurrence (ou la chaîne entière si le motif n'a pas de capture explicite), sans les positions de début/fin. Retourne nil si aucune occurrence n'est trouvée.

print(string.match("hello world", "%a+"))          --> hello
print(string.match("key = value", "(%w+)%s*=%s*(%w+)"))  --> key   value

string.gmatch(s, pattern [, init])

Retourne une fonction itératrice qui, à chaque appel, retourne les captures de l'occurrence suivante de pattern dans s (à partir de init, défaut 1), jusqu'à épuisement.

for word in string.gmatch("the quick brown fox", "%a+") do
    print(word)
end

string.gsub(s, pattern, repl [, n])

Remplace dans s les occurrences de pattern par repl, au maximum n fois (toutes par défaut). Retourne la chaîne résultante suivie du nombre de remplacements effectués. repl peut être :

print(string.gsub("hello world", "o", "0"))            --> hell0 w0rld   2
print(string.gsub("hello world", "%w+", string.upper)) --> HELLO WORLD   2
print(string.gsub("$name is $age", "%$(%w+)", { name = "Ana", age = "30" }))
                                                          --> Ana is 30   2

Motifs (« patterns »)

find, match, gmatch et gsub interprètent pattern avec le langage de motifs façon Lua — un sous-ensemble volontairement limité par rapport aux expressions régulières, mais suffisant pour l'essentiel des besoins de traitement de texte, et sans leurs pièges de performance.

Classes de caractères (un caractère quelconque parmi une catégorie) :

Classe Signifie Classe Signifie (négation, majuscule)
. N'importe quel caractère
%a Lettre %A Tout sauf une lettre
%d Chiffre %D Tout sauf un chiffre
%l Minuscule %L Tout sauf une minuscule
%u Majuscule %U Tout sauf une majuscule
%s Espace (espace, tab, saut de ligne) %S Tout sauf un espace
%w Alphanumérique %W Tout sauf alphanumérique
%p Ponctuation %P Tout sauf ponctuation
%c Caractère de contrôle %C Tout sauf un caractère de contrôle
%x Chiffre hexadécimal %X Tout sauf un chiffre hexadécimal
%x (x non alphanumérique) Le caractère x littéral (échappement, ex. %. pour un point)

Ensembles : [set] correspond à n'importe quel caractère de set ; [^set] à tout caractère absent de set. set peut contenir des caractères littéraux, des classes (%a, %d, ...) et des intervalles (a-z).

Quantificateurs, appliqués à l'item précédent (classe, ensemble, ou .) :

Suffixe Signifie
* 0 ou plus, aussi gourmand que possible
+ 1 ou plus, aussi gourmand que possible
- 0 ou plus, aussi peu que possible (paresseux)
? 0 ou 1

Ancres : ^ en tête de motif force la recherche à ne tenter qu'à la position de départ (pas de balayage). $ en fin de motif force la fin de l'occurrence à coïncider avec la fin de s.

Captures : (...) capture la sous-chaîne correspondante — accessible en résultat de find/ match/gmatch, ou via %1-%9 dans un gabarit de gsub. () (parenthèses vides) capture la position courante (un entier) plutôt qu'une sous-chaîne.

Constructions spéciales :

for k, v in string.gmatch("a=1, b=2, c=3", "(%a+)=(%d+)") do
    print(k, v)
end

print(string.match("(nested (parens) here)", "%b()"))

string.format(formatstring, ···)

Formatage façon printf en C. Chaque directive %[flags][largeur][.précision]conversion consomme un argument (sauf %%, qui insère un % littéral). Conversions supportées :

Conversion Signifie
d, i Entier signé
u Entier non signé
o Entier en octal
x, X Entier en hexadécimal (minuscules / majuscules)
c Caractère depuis son code numérique
s Chaîne (tostring implicite ; .précision tronque)
q Valeur formatée sous forme littérale YScript réinjectable dans du code
f, F Flottant, notation décimale
e, E Flottant, notation scientifique
g, G Flottant, notation la plus compacte entre f/e
a, A Flottant, notation hexadécimale

Drapeaux : - (aligne à gauche), + (force le signe), (espace pour un nombre positif), 0 (remplit avec des zéros), # (forme alternative — préfixe 0x/0, point décimal forcé...).

print(string.format("%5d|%-5d|", 42, 42))     -->    42|42   |
print(string.format("%.2f", 3.14159))         --> 3.14
print(string.format("%x", 255))               --> ff
print(string.format("%q", 'he said "hi"\n'))  --> "he said \"hi\"\n"

string.netformat(formatstring, ···)

Alternative à string.format utilisant la syntaxe de formatage composite .NET ("{0}", "{1,5}", "{2:F2}", ...) plutôt que la syntaxe C-printf — pour les scripts qui veulent accéder directement aux spécificateurs de format numériques/dates propres à .NET.

print(string.netformat("{0:F2} - {1,10}", 3.14159, "x"))

string.dump(function [, strip])

Sérialise function (qui doit être une fonction script, pas une fonction externe) en bytecode précompilé, retourné sous forme de chaîne binaire. strip (booléen), si vrai, retire les informations de débogage du bytecode produit. Le résultat peut être rechargé avec load.

string.pack(fmt, ···) / string.unpack(fmt, data [, pos]) / string.packsize(fmt)

Sérialisation binaire façon "struct" (compatible Lua 5.4). string.pack encode ses arguments selon fmt en une chaîne binaire ; string.unpack fait l'inverse à partir de la position pos (défaut 1) de data, en retournant les valeurs décodées suivies de la position juste après les données lues ; string.packsize calcule la taille en octets qu'occuperait string.pack(fmt, ···), sans options de taille variable (s, z).

Options de fmt (peuvent être précédées de modificateurs d'ordre des octets/alignement, voir plus bas) :

Option Signifie
b / B byte signé / non signé (1 octet)
h / H short signé / non signé (2 octets)
iN / IN Entier signé / non signé de N octets (défaut 4, max 16)
l / L Entier long signé / non signé (8 octets)
j / J lua_Integer signé / non signé (8 octets)
T size_t (8 octets)
f Flottant simple précision (4 octets)
d / n Flottant double précision (8 octets)
sN Chaîne précédée de sa longueur sur N octets (défaut 8)
z Chaîne terminée par un octet nul
cN Chaîne de taille fixe N octets (taille obligatoire)
x Un octet de remplissage (padding)
Xop Aligne selon la taille de l'option op sans rien écrire
< / > / = Octets faibles/forts en tête / boutisme natif
!N Alignement maximal à N octets
espace Ignoré (séparateur visuel)
local packed = string.pack("<i4i4", 10, 20)
print(#packed)                       --> 8
local a, b, pos = string.unpack("<i4i4", packed)
print(a, b, pos)                     --> 10   20   9