Initiation au Lua avec Scribunto/Premières notions

Début de la boite de navigation du chapitre

Dans ce premier chapitre, nous allons progressivement introduire le minimum nécessaire pour pouvoir commencer à écrire des petits programmes très simples en Lua.

Premières notions
Icône de la faculté
Chapitre no 1
Leçon : Initiation au Lua avec Scribunto
Retour auSommaire
Chap. suiv. :Mise au point d'un module

Exercices :

Premiers pas
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Initiation au Lua avec Scribunto : Premières notions
Initiation au Lua avec Scribunto/Premières notions
 », n'a pu être restituée correctement ci-dessus.

Premier module

modifier

L'espace module

modifier

Pour écrire un programme en Lua, nous avons, tout d’abord, besoin d'un endroit pour l'écrire. De même que les modèles s'écrivent dans des pages nommées : Modèle:«Nom de la page», les programmes Lua s'écriront dans des pages nommées : Module:«Nom de la page». On dira que l’on écrit dans l'espace module.

Une fois le programme écrit dans l'espace module, nous devons être capable de l’utiliser à partir d'une autre page (de même qu'un modèle peut être inclus dans une page en écrivant {{Nom du modèle}}).

Exemple de module

modifier

Nous allons prendre un exemple très simple pour bien comprendre. Supposons que l’on veuille écrire un programme qui, lorsqu'on l'appelle, nous renvoie le message : « Coucou, c’est moi ! ». Nous commencerons par créer une page du style : Module:Exemple simple et dans cette page, nous écrirons :

local p = {}

function p.Salutation()
    return "Coucou, c’est moi !"
end

return p

Nous allons vous expliquer ce que signifie chacune des lignes du module.

La table p

modifier

Tout d’abord, nous avons une première ligne : local p = {}. Le mot local est réservé par le langage Lua (de tels mots-clés apparaissent en couleur). Cela signifie que lorsqu’il sera employé, il aura un sens qui a été défini par les concepteurs du langage Lua. Ce mot nous indique que ce qui est écrit juste après n'est valable que dans la page ou à l'intérieur d'un bloc d'instructions (nous reviendrons sur cette notion quand nous préciserons ce que l’on entend par « bloc d'instructions »). Après le mot local, nous voyons : p = {}. p est une table (dans d'autres langages, on dit plutôt tableau). Dans un langage de programmation, une table (ou tableau) peut être vue comme une armoire avec des tiroirs, dans lesquels on peut ranger toutes sortes d'objets (nous verrons lesquels, plus tard). Comment savons-nous que p est une table ? C'est à cause de = {}. Les accolades ouvrantes et fermantes symbolisent une table en Lua (attention, ce n’est pas vrai dans tous les langages !). La ligne : local p = {} signifie donc : « p est une table qui ne peut être utilisée que dans ce module ».

 

Retenez bien, si vous ne le savez pas, que ce que vous apprenez ici est propre au langage Lua. Les créateurs de langages, assez mystérieusement, prennent plaisir à nous embrouiller en définissant les choses différemment. En langage C par exemple, un tableau ne se définit pas de la même manière.

La fonction p.Salutation

modifier

Nous avons ensuite les lignes :

function p.Salutation()
    return "Coucou, c’est moi !"
end

function est un autre mot-clé qui sert à définir une fonction. Ici, on définit la fonction p.Salutation qui sera rangée dans la table p (l'un des tiroirs de l'armoire p, pour reprendre l'analogie utilisée plus haut), c’est pour cela que l’on écrit function p.Saluation. Si l’on avait écrit seulement function Salutation(), on aurait bien défini une fonction Salutation mais elle ne se trouverait pas dans la table p. Il est nécessaire de mettre la fonction p.Salutation dans la table p pour que cette fonction puisse être retournée en même temps que le contenu de la table p grâce à l'instruction return p se trouvant en fin de programme (nous reviendrons là-dessus plus tard).

En dessous de la définition de la fonction, nous avons la ligne : return "Coucou, c’est moi !" qui constitue le bloc d'instructions décrivant le programme de la fonction. Nous disons bloc d'instructions car il y aurait pu y avoir plusieurs instructions si la fonction avait été plus complexe. Ce bloc d'instructions se terminant par le mot-clé end indiquant que la programmation de la fonction est terminée.

Si l’on analyse plus en détail l'instruction : return "Coucou, c’est moi !", nous voyons qu'elle débute par le mot-clé return. return indique ce qui doit être retourné par la fonction. Le rôle d'une fonction est, la plupart du temps, de retourner quelque chose. En mathématiques la fonction f(x) = 3x + 2 retourne le résultat du calcul de l’expression 3x + 2 en remplaçant x par un nombre particulier. En Lua, on programmerait cette fonction ainsi :

function f(x)
    return 3 * x + 2
end

Nous remarquons les parenthèses ouvrante ( et fermante ), présentes aussi dans notre programme dans la ligne : function p.Salutation(). Mais il n'y a rien à l'intérieur car la fonction p.Salutation se contente de retourner une phrase sans qu'on lui transmette aucune information (il n'en sera pas toujours ainsi).

Après le mot-clé : return, nous avons : "Coucou, c’est moi !", qui est la phrase que doit retourner la fonction p.Salutation. Nous remarquons la présence des guillemets qui indiquent que c’est une chaîne de caractères. Ces guillemets ont deux fonctions. D'abord, ils indiquent où commence la chaîne de caractères et où celle-ci finit. Ensuite, ils indiquent que l’on a bien affaire à une chaîne de caractères et pas à une variable (nous reviendrons là-dessus plus bas lorsque nous étudierons plus en détail ce qu'est une variable).

Utiliser un module

modifier

Dans notre programme, après l'écriture de la fonction, nous voyons apparaître la ligne : return p. Cette instruction, écrite en dehors de la fonction, indique que l’on retourne le contenu de la table p pour le rendre disponible en dehors du module. Nous précisons que nous retournons le contenu de la table p et pas la table p elle-même, celle-ci n'étant disponible qu’à l'intérieur du module à cause de l'instruction local p = {}.

Comment allons-nous pouvoir récupérer le contenu de la table p, à savoir la fonction p.Salutation en dehors du module ? Pour cela, il nous suffit d'écrire dans une autre page qui n’est pas dans l'espace module, la commande : {{#invoke:Exemple simple|Salutation}}.

En effet, en tapant : {{#invoke:Exemple simple|Salutation}}, on obtient bien : « Coucou, c’est moi ! ».

Nous retiendrons la syntaxe de cette commande sous la forme provisoire : {{#invoke:nom du module|nom de la fonction}}. Nous allons voir dans les paragraphes suivants que l’on peut y rajouter des paramètres.

Paramétrer une fonction

modifier

Exemple avec un seul paramètre

modifier

Pour illustrer le passage d'un paramètre à une fonction écrite dans un module et ceci à partir d'une page extérieure au module, nous allons écrire une nouvelle fonction dans un nouveau module que l’on appellera, par exemple, Module:Autre exemple.

Nous nous proposons cette fois d'écrire un programme qui va traduire les jours de la semaine en anglais.

Le programme à l'intérieur du Module:Autre exemple sera rédigé ainsi :

local p = {}

function p.traduit(frame)	
	if frame.args[1] == "Lundi" then return "Monday" end
	if frame.args[1] == "Mardi" then return "Tuesday" end
	if frame.args[1] == "Mercredi" then return "Wednesday" end
	if frame.args[1] == "Jeudi" then return "Thursday" end
	if frame.args[1] == "Vendredi" then return "Friday" end
	if frame.args[1] == "Samedi" then return "Saturday" end
	if frame.args[1] == "Dimanche" then return "Sunday" end
end

return p

Nous ne commenterons, bien sûr, que ce qui est nouveau par rapport au paragraphe précédent.

Dans la définition de la nouvelle fonction : function p.traduit(frame), nous remarquons que, cette fois, nous avons entre parenthèses, le mot frame.

Par convention, on peut dire que frame est une table qui contient les paramètres que l’on a passés au module depuis l'extérieur grâce à la commande vue au paragraphe précédent qui, dans ce paragraphe, aura la syntaxe plus complète suivante :
{{#invoke:''nom du module''|''nom de la fonction''|args[1]|args[2]|args[3]|etc.}}
. args[1], args[2], args[3], args[4] étant des informations que l’on souhaite transmettre à la fonction que l’on a choisie dans le module. Ces informations, appelées paramètres, se retrouveront dans le module dans la table frame. Pour y accéder, il suffira donc d'écrire dans le programme respectivement : frame.args[1], frame.args[2], frame.args[3], etc. Par exemple, supposons que nous voulions utiliser notre programme pour traduire « jeudi » en anglais. Nous écrirons simplement {{#invoke:Autre exemple|traduit|Jeudi}} et nous obtenons : « Thursday ».

Dans le corps de la fonction, nous avons cette fois un bloc d'instructions comprenant 7 instructions similaires et nous comprenons aisément que chacune de ces instructions correspond à un jour de la semaine. Prenons la première : if frame.args[1] == "Lundi" then return "Monday" end. Nous avons, dans cette instruction deux nouveaux mots-clés : if et then. if se traduit par « si » en français et then se traduit par « alors ». Le syntaxe générale de cette instruction est if condition then instructions end (ou, en français : « si condition alors instructions fin »). Autrement dit, de façon plus explicite : « si une certaine condition est réalisée alors le bloc d'instructions sera exécuté ».

Dans notre instruction : if frame.args[1] == "Lundi" then return "Monday" end, nous voyons que la condition est frame.args[1] == "Lundi". On se demande si le paramètre transmis à la fonction est la chaîne de caractère « Lundi ». En Lua, == signifie « égal » (si l’on met seulement =, le sens ne sera pas le même ; nous y reviendrons). Si la condition testée est remplie alors on exécutera le bloc d'instructions qui, dans notre exemple, se limite à return "Monday". Nous voyons que la fonction p.traduit va retourner la chaîne de caractères « Monday » si le paramètre transmis est « Lundi » et c’est ce que l’on voulait. Le raisonnement est le même pour les six autres lignes qui suivent.

Exemple avec plusieurs paramètres

modifier

Nous allons maintenant essayer de perfectionner le programme vu au paragraphe précédent en lui faisant traduire les jours de la semaine dans une langue que l’on aura choisie. Nous nous limiterons à l'anglais et à l'espagnol. Le lecteur comprendra aisément comment introduire d'autres langues.

Le programme est le suivant :

local p = {}

function p.traduit(frame)
	if frame.args[2] == "Anglais" then
		if frame.args[1] == "Lundi" then return "Monday" end
		if frame.args[1] == "Mardi" then return "Tuesday" end
		if frame.args[1] == "Mercredi" then return "Wednesday" end
		if frame.args[1] == "Jeudi" then return "Thursday" end
		if frame.args[1] == "Vendredi" then return "Friday" end
		if frame.args[1] == "Samedi" then return "Saturday" end
		if frame.args[1] == "Dimanche" then return "Sunday" end
	end
	if frame.args[2] == "Espagnol" then
		if frame.args[1] == "Lundi" then return "Lunes" end
		if frame.args[1] == "Mardi" then return "Martes" end
		if frame.args[1] == "Mercredi" then return "Miércoles" end
		if frame.args[1] == "Jeudi" then return "Jueves" end
		if frame.args[1] == "Vendredi" then return "Viernes" end
		if frame.args[1] == "Samedi" then return "Sábato" end
		if frame.args[1] == "Dimanche" then return "Domingo" end
	end
end

return p

Nous avons mis ce programme dans le Module:Traduction multilingue. Pour l’utiliser, nous devons préciser deux paramètres. Par exemple, pour obtenir la traduction de dimanche en espagnol, nous devons écrire dans la page appelante : {{#invoke:Traduction multilingue|traduit|Dimanche|Espagnol}}, ce qui nous donne : « Domingo ». À l'intérieur du programme, les deux paramètres sont accessibles respectivement par frame.args[1] et frame.args[2].

Le programme, en lui-même, ne présente pas de nouvelles instructions. Nous remarquerons toutefois la possibilité d’emboîter les structures if condition then instructions end.

Par exemple, la première structure concernée s'écrit sous la forme :

if frame.args[2] == "Anglais" then
	-- Bloc d'instructions --
end

Le bloc d'instructions comprenant les 7 traductions en anglais sous forme de 7 structures if condition then instruction end.

Concaténer du texte

modifier

La concaténation est une opération qui consiste à mettre bout à bout plusieurs chaînes de caractères. L'opérateur de concaténation est .. (deux points qui se suivent). Pour illustrer cela, nous allons écrire un Module:Faire part qui, à partir d'un ou deux noms entrés en paramètres, forme une phrase annonçant, soit une naissance, soit un mariage, soit un décès.

Le contenu du module est le suivant :

local p = {}

function p.naissance(frame)
	return "Nous avons la joie de vous annoncer la naissance de " .. frame.args[1] .. "."
end

function p.mariage(frame)
	return "Nous sommes heureux de vous annoncer le mariage de " .. frame.args[1] .. " et " .. frame.args[2] .. "."
end

function p.deces(frame)
	return "Nous sommes au regret de vous annoncer le décès de " .. frame.args[1] .. "."
end

return p

Nous remarquons que ce module contient, cette fois, trois fonctions. Donnons des exemples d'utilisation de ce module.

Si dans la page appelante, nous écrivons {{#invoke:Faire part|mariage|Louis|Christine}}, nous obtenons :

« Nous sommes heureux de vous annoncer le mariage de Louis et Christine. »

Si dans la page appelante, nous écrivons {{#invoke:Faire part|naissance|Noémie}}, nous obtenons :

« Nous avons la joie de vous annoncer la naissance de Noémie. »

Si dans la page appelante, nous écrivons {{#invoke:Faire part|deces|monsieur Bertelot}}, nous obtenons :

« Nous sommes au regret de vous annoncer le décès de monsieur Bertelot. »

Par exemple, pour le faire part de mariage, l'instruction return retourne une chaîne de caractères obtenue en concaténant avec .. :

  • "Nous sommes heureux de vous annoncer le mariage de " (on remarque la présence d'une espace en fin de chaîne pour éviter d’avoir le premier nom collé au mot « de ») ;
  • frame.args[1] (contenant dans notre exemple la chaîne de caractères formant le prénom Louis) ;
  • " et " (on remarque aussi les espaces en début et en fin de chaîne pour éviter d’avoir LouisetChristine) ;
  • frame.args[2] (contenant dans notre exemple la chaîne de caractères formant le prénom Christine) ;
  • "." (chaîne de caractères se limitant au point final de la phrase).

Dans ce module, nous remarquons que nous avons écrit la fonction p.deces sans accents. En effet, le Lua n'accepte pas les accents dans les noms de fonctions et plus généralement dans les noms de variables.

Variables, types et affectation

modifier

Nous allons maintenant aborder une notion importante qui est la notion de variable. D'une façon imagée, on pourrait dire qu'une variable est une boîte dans laquelle on va pouvoir mettre un objet particulier qui sera, soit une chaîne de caractères, soit un nombre, soit une table, soit une fonction, soit un booléen, etc. En Lua, une même variable peut, dans un même programme, contenir, par exemple, une chaîne de caractères dans une partie du programme et dans une autre partie, elle contiendra un nombre. Cette faculté de recevoir des objets de nature différentes se traduit en disant que les variables, en Lua, sont dynamiques. Ce n’est pas le cas, dans d'autres langages comme le C ou le Pascal où les variables sont dites statiques (chacune étant spécialisée pour recevoir un seul type d'objet).

La plupart du temps, une variable se déclare grâce à l'instruction :

local tirelire

Le mot local signifie qu'elle n'est opérationnelle que là où on l'a déclarée. Si on la déclare en début de module, elle sera valable dans tout le module (y compris dans les fonctions du module). Si on la déclare au début d'une fonction, elle sera valable uniquement dans la fonction (y compris dans les structures de contrôle comme if..then). Si on la déclare au début d'une structure de contrôle, elle sera valable uniquement dans la structure de contrôle.

Il est possible, à la déclaration, de l'initialiser, c'est-à-dire d'y mettre quelque chose dedans. Dans ce cas, la variable adoptera le type de ce que l’on met dedans.

Si on ne l'initialise pas, la variable sera, par défaut, d'un type particulier que l’on appelle nil et contiendra nil, ce qui signifie « rien ». Elle restera de ce type jusqu'à ce qu'on y mette quelque chose.

Par exemple, pour l'instruction :

local tirelire = "Billet de dix euros"

la variable tirelire sera du type chaîne de caractères et sera censée être du type chaîne de caractères jusqu'à ce que l’on y mette quelque chose qui ne soit pas une chaîne de caractère.

Le signe = présent dans cette instruction ne veut pas dire « égal » mais « affectation ». À la variable tirelire, on affecte la chaîne de caractères : "Billet de dix euros".

Si l’on écrit :

local tirelire = 10

la variable tirelire sera du type nombre et sera censée être du type nombre jusqu'à ce que l’on y mette quelque chose qui ne soit pas un nombre.

Même si une variable peut contenir à des moments différents, des objets de type différent, il faut malgré tout que l’on sache ce qu'elle est susceptible de contenir dans toutes les parties du programme. Dans certains cas, le programme peut ne pas fonctionner si la variable n'a pas le bon type au bon moment.

Prenons un exemple : écrivons un programme qui nous signale si un nombre n'a pas une valeur trop élevée. Dans le Module:Balance, écrivons le programme suivant :

local p = {}

function p.alerte1(frame)
	local poids = frame.args[1]
	local reponse = "Votre poids est acceptable"
	if poids > 54 then
		reponse = "Attention, vous commencez à grossir !"
	end
	return reponse
end

return p

Si l’on écrit, dans une autre page : {{#invoke:Balance|alerte1|56}}, on obtient : « Erreur Lua dans Module:Balance à la ligne 6 : attempt to compare number with string. »

Pour comprendre pourquoi le programme ne marche pas, nous allons revenir sur un exemple précédent. Reprenons, par exemple, le programme qui traduisait les jours de la semaine en anglais. Pour traduire jeudi en anglais, nous avions écrit : {{#invoke:Autre exemple|traduit|Jeudi}}. Jeudi est une chaîne de caractères et pourtant, nous n'avons pas écrit : {{#invoke:Autre exemple|traduit|"Jeudi"}}. La commande invoke a interprété jeudi comme étant une chaîne de caractères même si nous n'avons pas mis les guillemets. Pour simplifier l'écriture, la commande invoke interprète systématiquement ses arguments comme étant des chaînes de caractères. Par conséquent, lorsqu'on écrit : {{#invoke:Balance|alerte1|56}}, le programme reçoit comme argument la chaîne de caractères "56" et non pas le nombre 56. Par conséquent, l'instruction de notre programme :

local poids = frame.args[1]

affecte à la variable poids la chaîne de caractères "56", et l'instruction :

	if poids > 54 then
		reponse = "Attention, vous commencez à grossir !"
	end

compare donc une chaîne de caractères au nombre 54, ce qui n'a pas de sens.

Pour remédier à cet inconvénient, il faut, avant de faire la comparaison avec 54, transformer le contenu de la variable poids en nombre. Comment faire ? Heureusement, dans le Lua, nous disposons d'un certain nombre de petites fonctions préprogrammées que nous étudierons en détail dans les chapitres ultérieurs. Pour les besoins de la circonstance, nous allons utiliser l'une d'elles ici. Cette fonction est la fonction tonumber qui convertit une chaîne de caractères en nombre dans la mesure où cela est possible. Par exemple, elle convertira la chaîne de caractères "12" en nombre 12.

Nous pouvons mettre en œuvre cette fonction en écrivant dans notre programme :

	local poids = tonumber(frame.args[1])

Et nous écrirons donc, dans le Module:Balance, une nouvelle fonction p.alerte2, qui est la version corrigée de la fonction p.alerte1, ainsi :

local p = {}

function p.alerte2(frame)
	local poids = tonumber(frame.args[1])
	local reponse = "Votre poids est acceptable"
	if poids > 54 then
		reponse = "Attention, vous commencez à grossir !"
	end
	return reponse
end

return p

Maintenant, si dans une autre page, on écrit : {{#invoke:Balance|alerte2|56}}, on obtient : « Attention, vous commencez à grossir ! »

On constate que cette fois, ça marche !! (du moins le programme, pas le régime !)

Nous allons maintenant étudier une particularité du Lua. Dans une affectation, le Lua a la capacité de caractériser le type des variables automatiquement en fonction de ce qui est affecté. Si, par exemple, dans ce qui est affecté, il y a des signes opératoires, la variable sera de type nombre. S'il y a des concaténations, la variable sera du type chaîne de caractères.

Premier exemple :

compte = a + b

La variable compte sera automatiquement considérée comme étant du type nombre à cause de la présence de l'opérateur +, sans même s'occuper de ce que contiennent les variables a et b. Et ceci, même si a et b contiennent des chaînes de caractères. Si a contient la chaîne de caractères "1" et si b contient la chaîne de caractères "2", alors la variable compte contiendra, malgré tout, le nombre 3.

À noter toutefois que si a ou b ne contient pas quelque chose qui puisse être converti en nombre, alors cela génère une erreur et le programme s'arrête. Nous étudierons la gestion des erreurs dans un chapitre ultérieur.

Deuxième exemple :

panneau = indication..route

La variable panneau sera automatiquement considérée comme étant du type chaîne de caractères à cause de la présence de l'opérateur de concaténation .., sans même s'occuper de ce que contiennent les variables indication et route. Et ceci, même si indication et route contiennent des nombres. Si indication contient le nombre 1 et si route contient le nombre 2, alors la variable panneau contiendra, malgré tout, la chaîne de caractères "12".

Ceci étant dit, nous pouvons revenir à notre programme qui ne marchait pas. Nous avons dit qu’il faudrait que la variable poids contienne un nombre et pas une chaîne de caractères. Pour la transformer, compte tenu de ce que nous venons de dire, certains petits malins auraient pu écrire le programme ainsi :

local p = {}

function p.alerte1(frame)
	local poids = frame.args[1] + 0
	local reponse = "Votre poids est acceptable"
	if poids > 54 then
		reponse = "Attention, vous commencez à grossir !"
	end
	return reponse
end

return p

Ça marche ! Mais :

	local poids = frame.args[1] + 0

ne fait pas professionnel et ressemble à du bricolage. Nous abandonnerons donc cette idée au profil de :

	local poids = tonumber(frame.args[1])

La structure if..then..else

modifier

Nous venons d'étudier la structure if condition then instructions end. Il est possible de compléter cette structure en y rajoutant un petit élément qui est else et qui signifie « sinon » en français. La structure if condition then instruction1 else instruction2 end signifie en français : « si une condition est remplie exécuter instruction1 sinon exécuter instruction2 ». À titre d'exemple, reprenons notre Module:Balance que nous avons commencé à remplir.

Dans ce module, grâce à l'ajout de else, nous pouvons écrire une fonction p.alerte3, qui est une autre façon d'écrire la fonction p.alerte2, ainsi :

local p = {}

function p.alerte3(frame)
	local poids = tonumber(frame.args[1])
	local reponse
	if poids < 55 then
		reponse = "Votre poids est acceptable"
	else
		reponse = "Attention, vous commencez à grossir !"
	end
	return reponse
end

return p

Dans une autre page, si nous écrivons {{#invoke:Balance|alerte3|57}}, nous obtenons : « Attention, vous commencez à grossir ! »

Traiter plusieurs variables en une seule instruction

modifier

Il nous reste à voir une particularité intéressante du Lua qu'on ne trouve pas dans d'autres langages : c’est l'affectation simultanée de plusieurs variables. En effet, plutôt que d'écrire :

a = 2
b = 7

c'est-à-dire mettre la valeur 2 dans a, puis la valeur 7 dans b, on peut écrire :

a, b = 2, 7

et tout se passera comme si l’on avait simultanément mis 2 dans a et 7 dans b. Vous allez me dire : bof ! quel intérêt ?  

Il y a plusieurs intérêts à cela. Nous verrons certains de ces intérêts dans les chapitres suivants quand nous étudierons les fonctions qui retournent plusieurs valeurs.

Pour le moment, nous pouvons donner un exemple simple : supposons que nous voulions échanger le contenu de deux variables. En Lua, nous écrirons simplement :

a, b = b, a

Dans un autre langage qui n'a pas l'affectation simultanée, on aurait été tenté d'écrire :

a = b
b = a

Mais ça ne marche pas car le contenu de a est remplacé par le contenu de b dans la première affectation. Le contenu initial de a est donc perdu et ne pourra donc pas aller dans b à la deuxième affectation.

On peut aussi déclarer simultanément deux variables en les initialisant :

local a, b = 2, 7

ou déclarer simultanément deux variables sans les initialiser :

local a, b