Apprendre le Lua

Lua est un langage de script développé au Brésil au début des années 1990. Le code source d’un programme en Lua est traduit et exécuté sous forme de code byte par un interpréteur Lua. L’interpréteur lui-même est codé en C, ce qui permet aux programmes en Lua de disposer d’une performance élevée lors de l’exécution. Par ailleurs, l’API C permet d’intégrer le code Lua dans les programmes C/C++. Le Lua est un langage multiparadigmes permettant d’écrire du code impératif, fonctionnel et orienté objet.

La principale caractéristique distinctive du Lua est son intégration simple dans d’autres systèmes et langages. Le Lua s’est ainsi établi comme un « langage glue » et est utilisé dans de nombreux moteurs de jeux. Ce langage peut en outre être utilisé pour commander des serveurs Web comme Apache et nginx. Via l’interface CGI, le Lua est, d’autre part, souvent utilisé comme langage de programmation Internet indépendant. Par ailleurs, ce langage intervient également dans la programmation d’applications mobiles.

Apprendre à programmer en Lua : premiers pas avec ce langage de script

Le moyen le plus simple et le plus rapide d’apprendre à programmer en Lua consiste à exécuter du code Lua sur la page de démo interactive de Lua. Vous pouvez y tester tous les exemples de code Lua présentés dans la suite de l’article. Copiez l’un des exemples de code dans le champ de saisie puis cliquez sur « run » pour exécuter le code.

En procédant de la sorte, vous n’aurez pas à procéder à une installation. Si vous souhaitez utiliser Lua sur votre propre système, suivez les informations plus bas. Dans le cas contraire, rendez-vous directement à la section « Apprendre les bases du langage de script Lua ».

Préparer votre système pour apprendre le Lua

L’interpréteur Lua est composé d’un unique fichier binaire disponible dans l’invite de commande sous la commande « lua ». Ce fichier est enregistré sur le système et doit le cas échéant être intégré dans le chemin. Lua propose également des bibliothèques permettant l’intégration de code Lua dans les programmes C/C++.

Pour l’installation sous Mac et Linux, l’installation devra être réalisée à l’aide du gestionnaire de paquets « Homebrew ». Après avoir installé Homebrew sur votre système, utilisez la commande suivante dans l’invite de commande pour installer Lua :

brew install lua

Pour installer Lua sur un système Windows, utilisez l’installateur LuaDist.

Utiliser l’interpréteur Lua de façon interactive

Comme pour de nombreux autres langages de script, l’interpréteur Lua peut être exécuté de façon interactive. En mode interactif, l’interpréteur reçoit le code Lua dans l’invite de commande et l’exécute ligne après ligne. Les valeurs ainsi générées sont directement indiquées dans l’invite de commande. En tant qu’utilisateur, vous avez la possibilité de vérifier et de modifier les valeurs des variables. Cette approche convient donc tout particulièrement à un prototypage rapide. Pour démarrer l’interpréteur Lua en mode interactif, nous exécutons la commande suivante dans l’invite de commande :

# Démarrer l’interpréteur Lua en mode interactif
Note

Pour quitter à nouveau le mode interactif, saisissez la commande « os.exit() » ou appuyez sur la combinaison de touches [Ctrl]+[D].

Exécuter le script Lua pour apprendre le Lua avec l’interpréteur Lua

Plutôt que de saisir progressivement le code Lua dans l’invite de commande, vous pouvez également indiquer à l’interpréteur Lua d’exécuter un fichier de code source Lua complet. Pour ce faire, nous devons tout d’abord générer un fichier Lua et transmettre à l’interpréteur Lua le nom du fichier. L’interpréteur lit le code source inclut dans le fichier ligne après ligne et les exécute.

# Exécuter le script Lua
lua <nomfichier>.lua
Note

Les fichiers de code source Lua se terminent par l’extension de fichier « .lua ».

Rendre le script Lua directement exécutable pour le tutoriel avec le shebang

Sur le système d’exploitation Linux/UNIX/macOS, nous avons également la possibilité de rendre directement exécutable un fichier de code source Lua. Pour ce faire, il nous faut saisir un « shebang » à la première ligne du fichier Lua :

#!/usr/local/bin/lua
-- Code Lua pour l’exécution

Comme vous pouvez le constater, le shebang contient l’emplacement de stockage du fichier binaire Lua, dans notre cas « #!/usr/local/bin/lua ». Dans certains cas, l’emplacement de stockage sur votre système local peut être différent. Vous pouvez alors déterminer le lieu de stockage du fichier binaire Lua en utilisant la commande « which » dans l’invite de commande :

# Déterminer l’emplacement de stockage du fichier binaire Lua
which lua

Après avoir doté le script Lua d’un shebang, vous devez marquer le fichier comme exécutable par l’utilisateur. Pour cela, utilisez la commande suivante dans l’invite de commande :

# Marquer le fichier Lua comme exécutable
chmod u+rx <nomfichier>.lua

Exécutez ensuite le script Lua dans le répertoire actuel :

./<nomfichier>.lua
Conseil

Sur les systèmes de type Linux et UNIX comme macOS, l’astuce du shebang fonctionne avec la plupart des langages de script. Selon le même schéma, vous pouvez rendre directement exécutable des scripts Ruby ou Python.

Apprendre les bases du langage de script Lua

Lua est un langage multiparadigmes : le style sur lequel il est basé est impératif et fonctionnel. Ce langage est complètement dynamique, ce qui signifie qu’aucune distinction n’est opérée entre « compile time » et « run time ». Lua s’appuie uniquement sur une gestion dynamique de la mémoire : la taille d’un objet dans la mémoire peut évoluer au cours du processus. Un « Garbage Collector » (GC) libère l’espace disque qui n’est plus nécessaire de façon à épargner cette tâche au programmeur.

Apprendre à utiliser les commentaires dans les scripts Lua

Les commentaires sont des éléments essentiels dans tous les langages de programmation. Ils sont notamment utilisés pour :

  • esquisser les éléments de code,
  • documenter les fonctionnalités du code,
  • activer/désactiver les lignes de code.

En Lua, un commentaire d’une ligne commence avec un double tiret (--) et se poursuit jusqu’à la fin de la ligne :

-- cette ligne sera ignorée par l’interpréteur

En les combinant avec des doubles crochets « [[ » et « ]] », il est également possible d’écrire des commentaires multiples :

--[[
Ce commentaire
s’étend sur
plusieurs lignes.
]]

Valeurs et types pour le tutoriel Lua

À l’instar de la plupart des autres langages de script, Lua est également un langage dynamique standardisé. Les types ne relèvent pas des variables mais des valeurs. Chaque valeur dispose précisément d’un type, qu’il s’agisse d’un nombre, d’une séquence de caractères, d’une valeur logique, etc. Au total, Lua dispose d’un nombre de types gérable. Ils sont résumés dans l’aperçu suivant :

Type

Explication

number

Nombre décimal

string

Séquence de caractères

boolean

Valeur logique : « true » ou « false »

nil

Valeur non disponible ; le type a uniquement la valeur « nil »

function

Fonction

table

Type de données composé : liste/Array, Hash/Dictionary

thread

Coroutines

userdata

Type de données C défini par l’utilisateur

Lua connaît la syntaxe littérale pour les valeurs de tous les types à l’exception de « thread » et « userdata ». Pour déterminer le type d’une valeur, on utilise la fonction « type() ». Cette fonction indique le nom du type sous forme de chaîne de caractères. Voici quelques exemples :

type(42) -- Le type est `number`
type("Tutoriel Lua") -- Le type est `string`
type(false) -- Le type est `boolean`
type(var) -- Le type est `nil`, puisque `var` n’est pas défini
Note

Veuillez noter que dans les exemples de code suivants le premier élément d’une liste en Lua a l’index 1 au lieu de 0, comme dans la plupart des langages !

Apprendre à programmer avec Lua : expressions, variables et opérateurs

Une expression est évaluée par l’interpréteur et fournit une valeur. Les expressions associent des littéraux, des opérateurs, des variables et des appels de fonctions. Les expressions peuvent également être groupées avec des parenthèses « () ». En tant que langage dynamique, Lua détermine automatiquement le type de la valeur fournie. Voici plusieurs exemples d’expressions :

-- Opération arithmétique dans Lua
1 + 2 -- évalué en tant que la valeur `3`
-- Concaténation de chaînes de caractères dans Lua
'Walther' .. 'White' -- évalué comme `WaltherWhite`
-- Concaténation de chaînes de caractères avec conversion automatique d’un chiffre dans Lua
'Les ' .. 3 .. ' mousquetaires' -- évalué comme `Les 3 mousquetaires`
-- Test d’équivalence dans Lua
7 == '7' -- évaluer comme `false`
-- Test d’équivalence dans Lua
'petit' == string.lower('PETIT') -- évalué comme `true`
-- information de type dynamique
type(var) == 'nil' -- évalué comme `true`, puisque `var` n’est pas défini

Une variable est un nom pour une valeur dans la mémoire. Comme dans la plupart des langages de programmation, en Lua, un nom commence avec une lettre ou un tiret bas (_) suivi par d’autres lettres, tirets ou chiffres. Dans ce cas, on opère une distinction stricte entre les majuscules et les minuscules. Les mots réservés ci-dessous ne peuvent pas être utilisés seuls comme nom :

« and », « end », « in », « repeat », « break », « false », « local », « return », « do », « for », « nil », « then », « else », « function », « not », « true », « elseif », « if », « or », « until », « while »

Toutefois, ces mots réservés peuvent tout à fait être utilisés comme partie d’un nom :

for = "Peter" -- provoque une erreur
for_user = "Peter" -- est autorisé 

L’attribution d’une valeur à une variable est effectuée à l’aide de l’opérateur d’attribution (=). Celui-ci ne doit pas être confondu avec l’opérateur logique d’équivalence (==). Lors de l’attribution, on opère comme d’habitude une distinction entre « L-value » et « R-value » : la variable doit se trouver à gauche de l’opérateur d’attribution, il s’agit de la L-value. Cette variable est attribuée à la valeur évaluée de la R-value située à droite :

nombre = 13 -- correct
13 = nombre -- provoque une erreur puisque le nombre 13 ne peut pas se voir attribuer de nouvelle valeur

Un opérateur génère une nouvelle valeur à partir d’une ou plusieurs opérande(s). On parle alors d’opérateur unaire (à un chiffre) ou binaire (à deux chiffres). Un opérateur associe les opérandes d’un type donné et fournit une valeur d’un certain type. Jetons un œil aux différents opérateurs en Lua.

Les opérateurs arithmétiques opèrent sur des nombres et donnent un nombre :

Opérateur arithmétique

Arité

Opération

+

binaire

Addition

-

binaire

Soustraction

*

binaire

Multiplication

/

binaire

Division

%

binaire

Modulo

^

binaire

Exponentiation

-

unaire

Négation

Tous les opérateurs relationnels sont binaires et testent la façon dont deux opérateurs se comportent l’un par rapport à l’autre. Ils donnent une valeur logique :

Opérateur relationnel

Test

==

Équivalence

~=

Différence

>

Supérieur à

<

Inférieur à

>=

Supérieur ou égal à

<=

Inférieur ou égal à

Les opérateurs logiques associent des valeurs logiques et donnent une nouvelle valeur logique :

Opérateur logique

Arité

Opération

and

binaire

association ET

or

binaire

association OU

not

unaire

Négation

Il existe deux autres opérateurs spécifiques en Lua. Ils servent à la concaténation de chaînes de caractères (c’est-à-dire à leur enchaînement) ainsi qu’à la détermination de la puissance d’une valeur composée comme une table ou une chaîne de caractères :

Opérateur

Arité

Opération

..

binaire

Concaténation de chaînes de caractères

#

unaire

Déterminer le nombre d’éléments d’une table/la longueur d’une chaîne de caractères

Contrairement à de nombreux langages de script, Lua ne reconnaît pas les opérateurs d’attribution composés comme « += » et « -= ». Pour incrémenter et décrémenter des variables, l’opération doit être écrite de façon explicite :

prix = 42,99
remise = 0,15 -- 15 % de remise
prix -= prix * remise -- `-=` ne fonctionne pas dans Lua
-- La décrémentation doit être écrite de façon explicite
prix = prix - (prix * remise)

Comprendre les portées et les blocs pour le tutoriel Lua

Le concept de portée est un concept central dans tous les langages de programmation. Une variable n’existe que dans une certaine portée lexicale. Comme en JavaScript, en Lua, les variables sont globales par défaut. Toutefois, l’utilisation généralisée de variables globales est connue comme un « anti-modèle » qu’il convient d’éviter. En Lua, le mot-clé « local » fournit une solution. Il permet de limiter la portée d’une variable au bloc englobant, de façon similaire à la déclaration via « let » dans JavaScript.

-- cette variable est globale
x = 5
-- définir une variable locale
local z = 10

En Lua, les corps des fonctions et des boucles ouvrent une nouvelle portée. D’autre part, le Lua utilise le concept du bloc explicite. Un bloc définit une nouvelle portée pour le code situé entre les mots-clés « do » et « end ». Dans Java/C/C++, cela correspond aux accolades ouvrantes/fermantes « { » et « } ». L’exemple de code suivant illustre la manière dont les blocs, les portées et les variables fonctionnent ensemble :

-- portée externe
do
  local x = 1
  do -- portée interne
    local y = 2
    -- générer `z` dans la portée globale
    -- dans ce cadre, accès à la variable locale `x` de la portée externe
    -- et variable locale `y` de la portée interne
    z = x + y -- `z` a maintenant la valeur `3`
  end
  print(x) -- donne `1`
  print(y) -- donne `nil` puisque `y` n’existe pas dans la portée externe
  print(z) -- donne `3`
end
-- `z` est global et existe également en dehors de la portée externe
z = z + 4
print(z) -- donne `7`

Apprendre à programmer en Lua avec les structures de contrôle

Le Lua connaît les structures de contrôle habituelles que l’on retrouve dans d’autres langages de programmation. Ces structures incluent les boucles et les ramifications. Voici un exemple d’instructions « if », « then », « else », « elseif » pour Lua :

limit = 42;
nombre = 43;
if nombre < limit then
  print("En-dessous de la limite.")
elseif nombre == limit then
  print("Précisément à la limite…")
else
  print("Au-dessus de la limite !")
end

Outre la boucle « while » classique, le Lua connaît également sa contrepartie « repeat »-« until ». Cette instruction existe également dans Ruby. Elle exige l’inversion de la condition utilisée. Cela signifie qu’un « while » avec la condition « nombre </= limit » correspond à un « repeat »-« until » avec la condition « nombre > limit ». Attention avec l’instruction « repeat » ! Quelle que soit la condition, le corps de la boucle est exécuté au minimum une fois. Voici un exemple :

limit = 10
nombre = 1
while nombre <= limit do
  print("nombre:", nombre)
  nombre = nombre + 1
end
-- Attention : bien que `nombre` soit déjà supérieur à `limit`,
-- le corps de la boucle est exécuté une fois
nombre = 11
repeat 
  print("nombre:", nombre)
  nombre = nombre + 1
until nombre > limit

À l’instar de la plupart des langages de programmation impératifs, le Lua connaît également l’instruction « for » en plus de la boucle « while ». Deux formes sont utilisées ici : une variante similaire à C avec une variable de boucle ainsi qu’une variante avec itérateur. Regardons tout d’abord l’utilisation de l’instruction « for » avec la variable de boucle :

début = 1
fin = 10
for nombre = début, fin do
  print("Nombre actuel :", nombre) -- `1,2,3,4,5,6,7,8,9,10`
end
-- définir explicitement l’étape sur `2`
étape = 2
for nombre = début, fin, étape do
  print("Nombre actuel :", nombre) -- `1,3,5,7,9`
end
-- l’étape peut être négative
étape = -2
-- permuter le début et la fin avec une étape négative, pour compter de façon décroissante
for nombre = fin, début, étape do
  print("Nombre actuel :", nombre) -- `10,8,6,4,2`
end

De façon surprenante, la variable de boucle définie dans la boucle « for » est locale et non globale, sans qu’il soit pour autant nécessaire de la déclarer explicitement comme « locale ». C’est logique et, à cet égard, le Lua se démarque positivement du JavaScript. En JavaScript, une variable de boucle déclarée sans « let » ou « var » est globale, ce qui peut entraîner des erreurs sérieuses.

Regardons à présent la boucle « for » avec un itérateur en Lua. Sur le principe, l’approche ressemble à celle de Python : au lieu d’incrémenter une variable de boucle et de l’utiliser comme index dans une liste, nous itérons directement via les éléments de la liste. Pour générer l’itérateur, on utilise souvent la fonction « ipairs() ». Voici un exemple :

-- Définir une liste d’années
décennies = {1910, 1920, 1930, 1940, 1950, 1960, 1970, 1980, 1990}
-- accéder aux années individuelles à l’aide d’un itérateur
for index, année in ipairs(décennies) do
  print(index, année)
end

Fonctions : en apprendre plus avec Lua

De la même façon qu’en C/C++, Java et JavaScript, les fonctions sont définies avec le mot-clé « function ». Comme à l’accoutumée, les noms des fonctions sont suivis de leurs paramètres entre parenthèses. La particularité du Lua réside dans le fait qu’en cas d’appel d’une fonction avec précisément un littéral comme argument, il est possible d’omettre les parenthèses. En Lua, une fonction ne doit pas impérativement donner une valeur. Par définition, une fonction sans valeur est une « procédure » :

-- Définir la procédure
function bonjour(nom)
  print("Bonjour", nom)
end
-- Appeler la fonction
bonjour("très cher monsieur")
-- l’écriture suivante est également possible
bonjour "très cher monsieur"
-- toutefois, la syntaxe suivante ne fonctionne pas
nom = "Walther"
bonjour nom -- erreur de syntaxe
-- avec une variable à la place d’un littéral, la fonction doit être appelée avec des parenthèses
bonjour(nom)

Si l’on souhaite obtenir une valeur à partir d’une fonction, il convient d’utiliser le mot-clé « return » comme à l’accoutumée. Ce mot-clé met fin à l’exécution de la fonction et donne la valeur indiquée. Dans l’exemple de code suivant, un chiffre est mis au carré :

-- Fonction avec une valeur de retour unique
function carré(nombre)
  -- l’expression `nombre * nombre` est évaluée
  -- et sa valeur est obtenue
  return nombre * nombre
end
-- Mettre un nombre au carré
print(carré(9)) -- `81`

Comme en Python et en JavaScript, en Lua, une fonction peut recevoir un nombre variable d’arguments. Les arguments sont enregistrés dans une construction spéciale « (...) ». Pour accéder aux arguments, il est souvent utile de les rassembler dans une liste avec l’expression « {...} ». Il est également possible d’utiliser la fonction « select() » qui extrait un argument sous l’index indiqué. Le nombre d’arguments est déterminé avec l’expression « #{...} ».

-- imprimer tous les arguments d’une fonction
function var_args(...)
  for index, arg in ipairs({...}) do
    print(index, arg)
  end
end
var_args('Peter', 42, true)

Outre un nombre variable d’arguments, le Lua permet également de fournir plusieurs valeurs avec une instruction « return ». Le processus fonctionne à peu de chose près comme en Python, toutefois sans le type explicite « Tupel ». Comme en Python, il est fréquent d’attribuer plusieurs variables à la valeur de retour en cas d’appel de fonction. Voici un exemple :

-- Fonction avec plusieurs valeurs de retour
function premier_et_dernier(liste)
  -- fournit le premier et le dernier élément de la liste
  -- chaque valeur de retour est séparée par une virgule `,`
  return liste[1], liste[#liste]
end
personnes = {"Jim", "Jack", "John"}
-- Attribution des valeurs de retour à plusieurs variables
premier, dernier = premier_et_dernier(personne)
print("Le premier est", premier)
print("Le dernier est", dernier)

Si l’une des valeurs de retour n’est pas nécessaire, selon une convention usuelle, on utilise le tiret bas (_) comme balise, comme dans l’exemple suivant :

function min_moyenne_max(...)
  -- définir les valeurs de début pour `min` et `max` sur le premier argument
  local min = select(1, ...)
  local max = select(1, ...)
  -- Définir la valeur médiane sur zéro au début
  local moyenne = 0
  -- itérer sur les chiffres
  -- nous n’avons pas besoin de la variable index
  -- nous utilisons `_` en tant que balise
  for _, nombre in ipairs({...}) do
    -- définir un nouveau minimum le cas échéant
    if min > nombre then
      min = nombre
    end
    -- définir un nouveau maximum le cas échéant
    if max < nombre then
      max = nombre
    end
    -- additionner des chiffres pour la moyenne
    moyenne = moyenne + nombre
  end
  -- diviser la somme des nombres par leur nombre
  moyenne = moyenne / #{...}
  return min, moyenne, max
end
-- ici, nous n’avons pas besoin de la valeur `moyenne`
-- nous utilisons `_` en tant que balise
min, _, max = min_moyenne_max(78, 34, 91, 7, 28)
print("Le minimum et le maximum des nombres sont", min, max)

En Lua, les fonctions sont des « first-class citizens ». Cela signifie qu’elles peuvent être rattachées à des variables et peuvent également être transmises à d’autres fonctions comme arguments. D’autre part, une fonction peut servir de valeur de retour d’une fonction. Pris dans son ensemble, le Lua permet donc une programmation fonctionnelle, illustrée ici par l’exemple de la célèbre fonction « map() » :

-- fonction `map()` en Lua
-- reçoit une fonction `f` et une liste comme arguments
function map(f, liste)
  -- créer une nouvelle liste pour les valeurs de sortie
  local _liste = {}
  -- itérer sur les éléments de la liste avec index
  for index, valeur in ipairs(liste) do
    -- utiliser la fonction `f()` sur la valeur actuelle de la liste
    -- et enregistrer la valeur de retour dans la nouvelle liste sur le même index
    _liste[index] = f(valeur)
  end
  -- retourner une nouvelle liste
  return _liste
end
-- Liste de chiffres
nombres = {3, 4, 5}
-- Fonction appliquée à tous les éléments de la liste
function carré(nombre)
  return nombre * nombre
end
-- génération des carrés via la fonction `map()`
carrés = map(carré, nombres) -- `{9, 16, 25}`
-- obtenir les carrés
for _, nombre in ipairs(carrés) do
  print(nombre)
end

En programmation fonctionnelle, on utilise souvent la récursivité qui fait qu’une fonction s’appelle sans cesse elle-même avec des arguments modifiés. Dans le cas du Lua, il convient de faire preuve d’une attention particulière à cet égard. Les fonctions appelées de façon récursive doivent être explicitement déclarées comme « local ».

function f()
  -- appel récursif
  f() -- renvoie le cas échéant à une variable globale `f`
end
-- à la place
local function f()
  -- appel récursif
  f() -- renvoie à la fonction englobante
end
-- équivalente à
local f; -- Déclarer explicitement la variable `f` comme `local`
f = function() -- Attribution de la fonction à la variable locale `f`
  f() -- renvoie de façon garantie à la fonction englobante
end