Initiation au Lua avec Scribunto/Version imprimable
Une version à jour et éditable de ce livre est disponible sur la Wikiversité,
une bibliothèque de livres pédagogiques, à l'URL :
http://fr.wikiversity.org/wiki/Initiation_au_Lua_avec_Scribunto
Premières notions
Dans ce premier chapitre, nous allons progressivement introduire le minimum nécessaire pour pouvoir commencer à écrire des petits programmes très simples en Lua.
Premier module
[modifier | modifier le wikicode]L'espace module
[modifier | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]Exemple avec un seul paramètre
[modifier | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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 | modifier le wikicode]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
Mise au point d'un module
Dans le chapitre précédent, nous avons vu suffisamment de notions pour commencer à faire des petits modules faciles à utiliser. Bien souvent les modules ne seront pas nécessairement petits et faciles à utiliser. Leurs mises au point risque d’être délicates à faire. Par conséquent, avant d'aller plus loin dans l'étude du Lua avec Scribunto, nous allons consacrer ce chapitre à l'étude des moyens dont nous disposons pour faciliter la mise au point des modules.
Augmenter la lisibilité du programme se trouvant dans le module
[modifier | modifier le wikicode]Une première façon de rendre un programme plus facile à mettre au point est de l'écrire de façon à ce qu’il soit facile à relire par nous-même ou par quelqu’un d'autre. Par nous-même, car même si l’on a l'impression, sur le moment, de bien savoir ce qu’il contient, il se peut que l’on soit amené à y revenir après plusieurs mois et là, on risque d’avoir du mal à retrouver comment il fonctionne. Par les autres, car les modules écrits sur un des projets Wikimédia peuvent être améliorés par d'autres utilisateurs.
L'amélioration de la lisibilité d'un programme se base sur trois techniques:
L'indentation
[modifier | modifier le wikicode]C'est le fait de décaler vers la droite un bloc d'instructions pour le rendre plus lisible. On décalera vers la droite les instructions se trouvant à l'intérieur d'une fonction, d'une structure if condition then instruction end
, et les autres structures de contrôle que nous verrons plus tard.
Par exemple, dans le Module:Traduction multilingue, si nous avions écrit :
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
Le programme aurait, tout de même, bien fonctionné mais aurait été moins lisible.
Les noms de variable explicites
[modifier | modifier le wikicode]Le Lua, ainsi que la plupart des langages de programmation, permettent d'écrire les variables en utilisant plusieurs caractères. On donnera donc aux variables un nom qui exprimera ce qu'elles contiennent. Par exemple, une variable destinée à mémoriser un salaire s'appellera salaire.
Les commentaires dans le programme
[modifier | modifier le wikicode]Nous n'en n'avons pas parlé pour raison pédagogique, au chapitre précédent, car les programmes étaient petits et des commentaires les auraient alourdis inutilement. Mais il est possible et même fortement conseillé, quand les programmes deviennent plus longs, de rajouter des commentaires à l'intérieur même des programmes pour expliquer ce que chaque partie du programme fait.
Un petit commentaire tenant sur une ligne se fera en commençant par mettre un double tirets --
. On peut même mettre un commentaire après une instruction.
Par exemple, dans le Module:Faire part, on aurait pu rajouter des commentaires sur ce que réalisent les fonctions :
local p = {}
function p.naissance(frame) -- Faire part de naissance
return "Nous avons la joie de vous annoncer la naissance de " .. frame.args[1] .. "."
end
function p.mariage(frame) -- Faire part de mariage
return "Nous sommes heureux de vous annoncer le mariage de " .. frame.args[1] .. " et " .. frame.args[2] .. "."
end
function p.deces(frame) -- Faire part de décès
return "Nous sommes au regret de vous annoncer le décès de " .. frame.args[1] .. "."
end
return p
On peut aussi imaginer avoir besoin de plusieurs lignes pour faire un commentaire. Pour cela, on commencera le commentaire par --[[
et on le terminera par ]]
.
Par exemple pour Module:Exemple simple on aurait pu écrire :
local p = {}
function p.Salutation() -- [[mon commentaire ………………………………………………
de plusieurs lignes]]
return "Coucou, c’est moi !"
end
return p
L'éditeur Scribunto
[modifier | modifier le wikicode]Pour que les numéros de ligne de l'éditeur apparaissent, il faut cocher l'option « Activer la barre d'outils d'édition » dans les préférences de modification |
Étudions de plus près l'éditeur dont nous disposons dans l’extension Scribunto. Nous remarquons, tout d’abord, que chaque ligne est numérotée. Lorsque nous écrivons une ligne, celle-ci se détache sur un fond légèrement grisé. Nous le voyons à la figure 2, ligne 16 où se trouve le curseur. L'éditeur dispose d'une première correction pour détecter les erreurs grossières dans la syntaxe des instructions. Si nous n'écrivons pas correctement une instruction, le numéro en début de ligne se retrouve précédé d'une croix dans un carré rouge. Nous le voyons, par exemple, dans la figure 1, ligne 16. Si nous regardons, de plus près la ligne 16, nous verrons que nous avons oublié de mettre le mot-clé then
qui doit obligatoirement se trouver dans une structure if condition then instructions end
. Mieux que cela, si un carré rouge avec croix apparaît devant un numéro de ligne, nous pouvons avoir une indication sur le type d'erreur en promenant le curseur dessus (nous voulons dire par là, que nous pointons le carré rouge avec le curseur sans toutefois cliquer dessus). Dans notre exemple, figure 1, nous avons le message « [16:33] 'then' expected near 'return' », ce qui signifie que ligne 16, position 33, then
est attendu avant return
.
Nous remarquons aussi, en début de certaines lignes, un symbole ▼ juste après le numéro de ligne. Ce caractère permet de masquer un bloc d'instructions. Par exemple, si nous cliquons sur le ▼ de la ligne 3 ou est déclarée la fonction p.traduit, nous voyons disparaître toutes les instructions se trouvant entre cette déclaration et le end
indiquant la fin de l'écriture du contenu de la fonction. Si nous cliquons sur le ▼ de la ligne 4, nous verrons disparaître toutes les instructions du bloc if
. L'utilité de cette fonctionnalité est double. On peut ainsi masquer certaines parties du programme sur lesquelles on n’est pas en train de travailler. On peut aussi, dans un programme, où il y a beaucoup de structures emboîtées et par conséquent beaucoup de end
, s'assurer que l’on ne s'est pas « emmêlé les pinceaux » avec les end
.
Une autre particularité intéressante de l'éditeur est que lorsque l’on clique juste après une parenthèse, un crochet ou une accolade ouvrante ou fermante, nous voyons un léger encadrement sur la parenthèse, le crochet ou l'accolade fermante ou ouvrante correspondante. Cela peut être utile dans les expressions ayant beaucoup de parenthèses, crochets et accolades pour éviter les erreurs.
Intéressons-nous maintenant à ce qui apparaît sous le cadre de visualisation. Nous n'allons pas nous intéresser à ce qui est juste en dessous du cadre de visualisation, car il n'y a là rien de bien nouveau. Nous allons nous intéresser à ce qui se trouve plus bas dans le cadre noté « Aperçu de la page avec ce modèle » (voir figure 2). En effet, nous allons pouvoir, avec cet aperçu, voir ce que va donner le module avant même de devoir l'enregistrer. Il est possible, grâce à ce cadre, de faire l'écriture et la mise au point complète du module sans faire une seule édition.
Pour cela, enregistrez tout d’abord dans une page — par exemple Bac à sable — la commande {{#invoke:''nom du module''|''nom de la fonction''|"arguments"}}
concernant votre module. Dans le cadre « Aperçu de la page avec ce modèle » (voir ci-contre), écrivez le titre de la page où le module est invoqué (ici la page « Bac à sable »). Enfin, cliquez sur « Afficher l'aperçu ». L'aperçu de la page où vous avez invoqué votre module (ici l'aperçu de Bac à sable) s'affiche en haut de la page. Si ce n’est pas correct, vous pouvez corriger le module et cliquer à nouveau sur « Afficher l'aperçu » autant de fois que vous voulez, jusqu’à ce que le module soit au point. Une fois le module au point, vous pouvez cliquer sur le bouton « Enregistrer » et le module sera édité.
Le traitement des erreurs de script
[modifier | modifier le wikicode]Après avoir corrigé toutes les erreurs indiquées par l'éditeur, nous ne sommes peut-être pas au bout de nos peines. En essayant le programme, nous voyons apparaître le charmant message : « Erreur de script : vous devez spécifier une fonction à appeler. »
Cela signifie que nous avons malgré tout fait une erreur que l'éditeur n'a pas décelée mais qui rend l'exécution du programme impossible.
Avec l'expérience, nous pouvons éviter les principales erreurs de script. En attendant d'acquérir cette expérience, nous nous contenterons d'énumérer les principales situations qui provoquent une erreur de script.
Voici une liste à compléter éventuellement :
- Utilisation d'une variable en croyant qu'elle contient un certain type de données, alors qu'elle en contient un autre. Exemple : comparaison d'une variable contenant une chaîne de caractères avec un nombre.
- Utilisation d'une instruction en dehors du contexte où elle devrait être normalement utilisée. Par exemple, emploi de
frame.args[1]
en dehors de la fonction qui devrait normalement recueillir l'argument. - La fonction appelée n'existe pas. Vous avez, peut-être, fait une faute d'orthographe en écrivant son nom ou simplement oublié
p.
en début de nom. - Opération avec une variable, qui est bien du bon type, mais que l’on n'a pas initialisée et qui est donc vide au moment où on l'utilise.
- Peut éventuellement être produit par l'oubli de l'instruction
return
dans une fonction (selon comment est utilisée la fonction).
Lorsqu'une erreur de script se produit, vous pouvez avoir une première indication sur la provenance de cette erreur en cliquant sur le message : « Erreur de script : vous devez spécifier une fonction à appeler. » (bien qu’il soit rouge et non bleu). L'indication vous permettra, peut-être, de corriger rapidement l'erreur.
Si, malgré tout, l'erreur de script continue à apparaître et que vous ne voyez pas d'où elle provient, vous pouvez utiliser l'astuce suivante :
Vous mettez --
progressivement au début des lignes, en commençant par celles qui paraissent les plus douteuses, jusqu'à ce que l'erreur de script disparaisse. Ces lignes commençant par --
seront alors interprétées comme étant des commentaires et ne pourront plus provoquer d'erreur de script. Vous pourrez ainsi repérer la ligne qui provoque l'erreur de script.
La recherche d'une erreur dans le programme
[modifier | modifier le wikicode]Vous avez écrit un module. L'éditeur n'a pas détecté d'erreur et lorsque vous lancez l'exécution, vous n'avez pas le message : Erreur de script. Le problème, c’est que ce que vous fournit le programme n’est pas conforme à votre attente. Vous avez commis une erreur en écrivant le programme ! Vous essayez donc, dans un premier temps, de relire ce que vous avez écrit pour essayer de comprendre pourquoi cela ne marche pas. Au bout d'un certain temps de réflexion, vous vous rendez à l'évidence, vous n'arrivez pas à comprendre pourquoi cela ne marche pas. Nous allons donc étudier, dans ce paragraphe, des moyens dont nous disposons pour faciliter la recherche de l'erreur.
Introduction d'une variable espion
[modifier | modifier le wikicode]Nous avons d’abord une technique simple qui consiste à introduire dans le programme une variable supplémentaire, que l’on appellera rapport par exemple, dans laquelle vous allez, en certains points du programme, concaténer le contenu d'autres variables. À la fin de la fonction, au lieu de retourner la variable prévue, on retournera la variable rapport qui nous fournira ainsi une information sur le contenu des variables en certains points du programme et nous permettra de localiser plus précisément dans quelle partie se trouve l'erreur. Une fois que nous avons localisé de façon plus précise la partie du programme défaillante, nous pouvons recommencer en concaténant, dans notre variable rapport, plus d'informations sur la partie fautive. Et ainsi de suite jusqu'à repérer l'instruction qui est la cause de nos soucis.
Console de débogage
[modifier | modifier le wikicode]Lorsque nous sommes en mode modification dans un module, nous avons vu que nous avions un certain nombre de possibilités. Si nous continuons à descendre dans la page, tout en bas, nous découvrons un encadré noté Console de débogage représenté ci-dessous :
Nous allons étudier comment cela fonctionne.
Nous commencerons avec le premier exemple dans le premier chapitre, c'est-à-dire la fonction p.Salutation dans le Module:Exemple simple.
Après s'être mis en modification et être descendu jusqu'à la console de débogage, taper à l'intérieur de celle-ci :
=p.Salutation()
puis appuyer sur la touche « Entrée ». Nous voyons alors que ce que l’on a écrit remonte au-dessus de la zone grisée. Puis après un léger temps d'attente apparaît, toujours au-dessus de la zone grisée : « Coucou, c’est moi ! »
Nous avons donc pu tester notre programme. À ce niveau, si quelque chose s'était mal passé, nous aurions eu un message d'erreur nous indiquant la nature de l'erreur et la ligne où l'erreur s'est produite.
Si nous avions tapé :
=p.Salutation
sans les accolades, nous aurions eu comme réponse : function
. Nous pouvons avoir ainsi la nature des variables se trouvant dans le programme.
On aurait pu aussi taper :
print(p.Salutation())
ce qui est équivalent à =p.Salutation()
.
On peut même se servir de la console de débogage comme calculatrice. En effet, si l’on rentre :
=2+3
Elle nous répond 5
.
Faisons maintenant une petite expérience et rajoutons la ligne : mw.log("Il fait beau !")
dans notre programme ainsi :
local p = {}
function p.Salutation()
mw.log("Il fait beau !")
return "Coucou, c’est moi !"
end
return p
Dans la console de débogage tapons à nouveau :
=p.Salutation()
Après nous avoir prévenu que nous avons modifié le programme nous obtenons :
- Il fait beau !
- Coucou, c’est moi !
mw.log
est une commande qui nous permet de transmettre des messages à la console de débogage.
L'intérêt de la fonction mw.log
sur l'instruction return
est que la fonction mw.log
ne nous fait pas sortir du programme comme return
lorsqu'elle est utilisée. On va donc pouvoir utiliser la fonction mw.log
en plusieurs points du programme pour ramener plusieurs informations visibles sur la console de débogage. On peut ainsi construire tout un rapport d'exécution du programme qui apparaîtra sur la console de débogage et nous permettra ainsi de mettre au point le programme.
Ci-dessous, nous représentons la console de débogage après avoir tapé toutes les opérations décrites ci-dessus :
Le programme précédent était simple à étudier car c’était un programme sans paramètre.
Compliquons un peu les choses en étudiant maintenant la fonction p.traduit
se trouvant dans le Module:Autre exemple.
Cette fonction, pour fonctionner, doit recevoir en argument un jour de la semaine. Pour parvenir à transmettre cet argument, nous devons taper dans la console de débogage :
frame = mw.getCurrentFrame()
puis « Entrée ». Le message tapé remonte au dessus de la zone grisée. Nous tapons ensuite :
newFrame = frame:newChild{ args = { 'Jeudi' }}
puis « Entrée ». Le second message tapé remonte au dessus de la zone grisée. Nous tapons alors :
=p.traduit( newFrame )
Puis « Entrée ». Le troisième message tapé remonte au dessus de la zone grisée mais, cette fois, apparaît en plus :
- Thursday
Qui est bien la traduction de jeudi en anglais.
Ci-dessous, nous représentons la console de débogage après avoir tapé toutes les opérations décrites ci-dessus :
Attention, la série des deux commandes :
frame = mw.getCurrentFrame()
newFrame = frame:newChild{ args = { 'Jeudi' }}
doit être retapée si vous modifiez le programme pour faire des essais (introduction d'une fonction mw.log
par exemple).
Compliquons encore les choses en essayant de faire une simulation de débogage de la fonction p.alerte2
se trouvant dans le Module:Balance.
Supposons que le programme ne marche pas (c'est pas vrai ! mais on fait semblant). Pour essayer de comprendre pourquoi le programme ne marche pas, nous allons visualiser sur la console de débogage le contenu de toutes les variables se trouvant dans le programme (en fait, ici, il n'y en a que deux).
Nous utiliserons donc la fonction mw.log
à deux endroits différents pour visualiser les contenus des variables poids et reponse. Le programme sera ainsi complété :
local p = {}
function p.alerte2(frame)
local poids = tonumber(frame.args[1])
mw.log("Le poids rentré est ", poids)
local reponse = "Votre poids est acceptable"
if poids > 54 then
reponse = "Attention, vous commencez à grossir !"
end
mw.log("Le contenu de la variable reponse est : ", reponse)
return reponse
end
return p
Dans la console de débogage, nous taperons successivement les trois commandes :
frame = mw.getCurrentFrame()
newFrame = frame:newChild{ args = { '55' }}
print(p.alerte2( newFrame ))
Après ces opérations, la console de débogage se présente ainsi :
où nous voyons clairement apparaître le contenu des variables poids et reponse, ce qui nous permettra éventuellement de mieux comprendre d'où provient l'erreur (s'il y en avait une).
Tables et fonctions
Nous allons, dans ce chapitre étudier plus en détail les tables et les fonctions que nous avons entrevues dans les chapitres précédents. Une table, c’est pour ainsi dire, une supervariable qui a la faculté de contenir plusieurs objets, au lieu d'un seul, comme les autres variables. La table est, pour ainsi dire, le point fort du Lua par rapport aux autres langages à cause des possibilités offertes qui sont plus nombreuses que pour les autres langages. En contrepartie, cette notion est plus dure à assimiler pour le Lua que pour les autres langages. Nous allons donc procéder par étapes, dans ce chapitre, en commençant par les notions simples et en compliquant au fur et à mesure. Le lecteur n’est pas obligé de tout assimiler. Comprendre les premières notions lui permettra de traiter les tables en Lua comme elles sont traitées dans les autres langages et cela peut lui être suffisant pour programmer correctement. Nous approfondirons aussi les fonctions. Si nous étudions ces deux notions dans un même chapitre, c’est parce-qu’il y a une certaine interconnexion entre tables et fonctions. En effet, on peut faire des tables de fonctions et les fonctions peuvent recevoir des tables en arguments. Il est donc difficile de décider quoi étudier en premier !
Définition élémentaire d'une table
[modifier | modifier le wikicode]Au premier chapitre, nous avons effleuré la définition d'une table lorsque nous avons étudié l'instruction
local p = {}
Mais une table ne s’appelle pas forcément p. On peut lui donner un nom plus parlant sur sa fonction. Par exemple :
local semaine = {}
Et il est possible de l'initialiser :
local semaine = {"lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"}
On aurait pu, tout aussi bien, créer une table de nombres. Par exemple :
local nombres_premiers = {2,3,5,7,11,13,17,19,23,29,31,37,41}
Comment accéder à un élément d'une table. Il suffit d'écrire le nom de la table suivi de la position entre crochets de l’objet auquel on souhaite accéder. Ce genre de table, indexée par la position de l'objet, s’appelle une séquence en Lua.
Par exemple, pour notre première table, semaine[3] représente "mercredi". C'est le troisième jour de la semaine.
Pour notre deuxième table ; nombres_premiers[5] représente le cinquième nombre premier qui est 11.
Prenons un exemple pour illustrer ce que l’on vient de dire :
Écrivons un programme qui traduit un jour de la semaine en anglais. On l'a déjà fait au premier chapitre, mais cette fois écrivons un programme qui à partir seulement d'un nombre, nous donne une phrase indiquant la traduction du jour de position le nombre donné. Par exemple si l’on rentre 4, on doit obtenir la phrase : "La traduction de jeudi, en anglais est thursday" car jeudi est le quatrième jour de la semaine.
Dans le Module:Traduit écrivons :
local p = {}
local semaine = {"lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"}
local week = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}
function p.baratin1(frame)
index = tonumber(frame.args[1])
return "La traduction de "..semaine[index]..", en anglais, est "..week[index]
end
return p
En écrivant : {{#invoke:Traduit|baratin1|5}}, nous obtenons : La traduction de vendredi, en anglais, est friday
Index évolués
[modifier | modifier le wikicode]Nous venons de voir que les index des tables peuvent s'écrire à l'aide de nombres. Mais il est possible aussi de les écrire à l'aide de chaîne de caractères. Reprenons notre Module:Traduit. Nous pouvons alors imaginer un moyen astucieux de traduire un mot en anglais en se servant de ce mot directement comme index d'une table qui contiendrait toutes les traductions. Par exemple, dans une table tab avec l'index "chien", on accéderait à l'emplacement tab["chien"] où se trouverait la chaîne de caractère dog
Sur ce principe, écrivons donc un nouveau programme de traduction des jours de la semaine en anglais :
local p = {}
local sem = { ["lundi"] = "monday", ["mardi"] = "tuesday", ["mercredi"] = "wednesday", ["jeudi"] = "thursday", ["vendredi"] = "friday", ["samedi"] = "saturday", ["dimanche"] = "sunday"}
function p.anglais(frame)
local jour = frame.args[1]
return "La traduction de "..jour..", en anglais, est "..sem[jour]
end
return p
En écrivant : {{#invoke:Traduit|anglais|samedi}}, nous obtenons : La traduction de samedi, en anglais, est saturday
Nous remarquons comment la table sem a été pré-remplie. Nous sommes obligés de bien préciser à quel indice correspond quelle information.
Si, dans un programme, nous sommes amenés à accéder directement à une donnée de la table sans passer par le biais d'une variable, nous pouvons l'écrire autrement. Par exemple, au lieu d'écrire sem["mardi"], on peut écrire, plus simplement, sem.mardi.
Pour bien vérifier l'équivalence de deux notations, nous avons rajouté une fonction dans le Module:Traduit :
function p.information(frame)
return "Le premier jour de la semaine anglaise est "..sem["lundi"].." et le dernier jour est "..sem.dimanche
end
En écrivant : {{#invoke:Traduit|information}}, nous obtenons : Le premier jour de la semaine anglaise est monday et le dernier jour est sunday
Nous avons utilisé les deux notations dans la même phrase pour vérifier qu’elles sont bien équivalente.
Par contre, si dans la fonction p.anglais, nous avions écrit sem.jour au lieu de sem[jour], nous aurions une erreur de script car jour est une variable qui contient une chaîne de caractères, mais n'est pas, elle-même, une chaîne de caractères.
Question : Lorsque les index sont des nombres, ne peut-on pas accéder à un élément de la table en écrivant sem.3 au lieu de sem[3]. Réponse : Non, car sem.3 est équivalent à sem["3"], mais pas à sem[3]. |
La notation que l’on vient de voir se répercute aussi sur la déclaration des tables. C'est-à-dire que la table sem de notre Module:Traduit aurait tout aussi bien pu s'écrire sous la forme simplifiée suivante :
local p = {}
local sem = { lundi = "monday", mardi = "tuesday", mercredi = "wednesday", jeudi = "thursday", vendredi = "friday", samedi = "saturday", dimanche = "sunday"}
function p.anglais(frame)
local jour = frame.args[1]
return "La traduction de "..jour..", en anglais, est "..sem[jour]
end
return p
Nous voyons, par exemple, que ["lundi"] a été remplacé par lundi.
Ce que nous avons dit n’est pas tout à fait vrai. Si nous sommes obligé d'utiliser, comme index, une chaîne de caractères ayant un accent alors les deux notations ne sont plus équivalentes. Par exemple sem["Nénuphar"] ne peut pas être remplacé par sem.Nénuphar qui sera rejeté par l'éditeur. Il en est de même pour la déclaration des tableaux. Ci-dessus nous avons eu de la chance car aucun des jours de la semaine ne prend d'accent. |
Tables de tables
[modifier | modifier le wikicode]On peut aussi manipuler des tables de tables, c'est-à-dire une table contenant des tables.
Si, par exemple, on veut déclarer une table contenant quatre tables, on écrira :
local t = {{},{},{},{}}
Mathématiquement, ce genre de table peut correspondre à des matrices.
Par exemple, la matrice :
se déclarera :
local A = {{2,1,-4,6},{5,-3,-2,4},{1,3,-4,7},{-5,3,2,5}}
Si l’on veut accéder au nombre -2 se trouvant à la deuxième ligne, troisième colonne, on écrira A[2][3].
Si l’on déclare une table (ou matrice) B ainsi :
local B = {}
Et que l’on écrit dans le programme, par exemple :
B[3][1] = 9
On obtiendra une erreur de script.
Fonctions relatives aux tables
[modifier | modifier le wikicode]Dans le chapitre 8, nous étudierons plus en détail d'autres fonctions relatives aux tables, en particulier les fonctions :
table.insert(t, ligne)
: incrémente la table avec "ligne".table.remove(t,ligne)
: retire un élément de la table.table.concat(t, séparateur)
: convertit la table en une chaîne de caractères, en séparant chaque ligne par une éventuelle autre chaîne.table.maxn(t)
: Retourne le plus grand index numérique positif utilisé dans la table.table.sort(t)
: Permet de trier la table.table.getn(t)
: renvoie la taille de la table.
Complément sur les fonctions
[modifier | modifier le wikicode]Fonctions appelées par une fonction
[modifier | modifier le wikicode]Nous avons déjà bien étudié les fonctions. Mais toutes les fonctions que nous avons vues jusqu'à maintenant étaient placées d'office dans une table que nous avons appelée p pour les besoins de #invoke (Ce qui nous montre déjà que l’on peut faire des tables de fonctions). Nous allons voir maintenant qu'une fonction peut exister sans être dans une table. Une fonction peut être appelée simplement par une autre fonction. Prenons un exemple :
Dans le Module:Fonction, écrivons une fonction qui calcule automatiquement les carrés des 4 premiers nombres premiers en prenant soin de mettre à part la fonction qui élève au carré.
local p = {}
function f(x)
return x^2
end
function p.carre1(frame)
local reponse = "<u>Nombres premiers élevés aux carrés</u> <br />"
reponse = reponse.."Le carré du nombre 2 est "..f(2).."<br />"
reponse = reponse.."Le carré du nombre 3 est "..f(3).."<br />"
reponse = reponse.."Le carré du nombre 5 est "..f(5).."<br />"
reponse = reponse.."Le carré du nombre 7 est "..f(7).."<br />"
return reponse
end
return p
La fonction f se contente d'élever au carré le nombre x, qui représente son argument, et nous voyons que cette fonction est appelée dans la fonction p.carre1 qui se trouve dans la table p.
En écrivant : {{#invoke:Fonction|carre1}}, nous obtenons :
Nombres premiers élevés aux carrés
Le carré du nombre 2 est 4
Le carré du nombre 3 est 9
Le carré du nombre 5 est 25
Le carré du nombre 7 est 49
Paramètres et valeurs retournées par une fonction
[modifier | modifier le wikicode]Les fonctions peuvent recevoir plusieurs paramètres. Pour passer plusieurs paramètres à une fonction, il suffit de les écrire simplement en les séparant par des virgules. Par exemple :
function f(x,y,z)
return x^2+y^2+z^2
end
Plus remarquable encore, une fonction peut retourner plusieurs valeurs en les séparant par des virgules :
function f(x)
return x^2,2x,5x-3
end
Nous voyons que la forme que prennent les valeurs en sortant est similaire à la forme que prennent les paramètres à l'entrée. Nous pouvons mettre ceci à profit en emboîtant des fonctions (fonctions composées). Prenons un exemple pour voir si cela marche bien :
local p = {}
function g(x,y,z)
return 2*x+y+3*z
end
function h(x)
return x,2*x,x
end
function p.composition(frame)
return g(h(frame.args[1]))
end
return p
Nous voyons que la fonction h a un seul paramètre mais retourne trois valeurs. La fonction g, qui traite trois paramètres, peut recevoir les trois valeurs retournées par h et nous renvoie une valeur. Nous pouvons vérifier que ça marche bien avec les deux exemples suivants :
En écrivant : {{#invoke:Fonction|composition|5}}, nous obtenons : 35
En écrivant : {{#invoke:Fonction|composition|3}}, nous obtenons : 21
Il nous reste tout de même un petit problème : Commet récupérer les valeurs retournées par une fonction qui retourne plusieurs valeurs. C'est là que nous allons utiliser l'affectation simultanée de plusieurs variables que nous avons entrevue à la fin du premier chapitre. Supposons que f soit une fonction qui retourne trois valeurs par exemple et que nous voulions récupérer les valeurs retournées pour f(3) par exemple. Nous écrirons tout simplement:
a,b,c = f(3)
et les trois valeurs retournées par f seront respectivement dans les trois variables a, b, c.
Une question vient à l'esprit : Et si nous ne sommes intéressés que par la première et la troisième valeur ? Dans ce cas nous écrirons :
a,,c = f(3)
Et si nous ne sommes intéressé que par la première valeur ? Dans ce cas, nous écrirons :
a = f(3)
Et si nous ne sommes intéressé que par la troisième valeur ? Dans ce cas, nous écrirons :
,,c = f(3)
Et si nous ne sommes intéressé que par les deux premières valeurs ? Dans ce cas, nous écrirons :
a,b = f(3)
Et ainsi de suite !!
Fonctions récursives
[modifier | modifier le wikicode]En Lua, comme en C ou en Pascal (mais pas en Fortran, ni en Cobol), les fonctions peuvent s'appeler elles-mêmes. On appelle ce phénomène la récursivité. Prenons l'exemple classique de la fonction factorielle.
local p = {}
function fact(n)
if n == 0 then
return 1 -- on renvoie la valeur 1 quand le paramètre vaut 0
else
return n * fact(n - 1)
end
end
function p.factorielle(frame)
return fact(frame.args[1])
end
return p
La fonction récursive est la fonction fact qui a pour argument n et qui fait appel à fact(n-1) si n est différent de 0. Cette fonction va s'appeler elle-même jusqu'à ce que son argument soit nul. Par exemple, si l’on veut calculer factorielle de 4 que l’on note 4!, on aura, en suivant le cheminement de la fonction :
4! = 4✕3! = 4✕3✕2! = 4✕3✕2✕1! = 4✕3✕2✕1✕0! = 4✕3✕2✕1✕1 = 4✕3✕2✕1 = 24
En écrivant : {{#invoke:Calcul|factorielle|5}}, nous obtenons : 120 car 120 = 5✕4✕3✕2✕1
Question : Dans le programme précédent, n'aurait-on pas pu rendre directement la fonction p.factorielle récurssive au lieu de lui faire appeler une autre fonction récursive, ici la fonction fact ? Réponse : Non, car la fonction p.factorielle a pour argument frame et est donc, par conséquent, dédiée à recevoir des informations de l'extérieur du module et pas de l'intérieur. Elle n'est donc pas appelable de l'intérieur du module et donc ne peut pas s'appeler elle-même. |
Tables de fonctions
[modifier | modifier le wikicode]En lua, nous pouvons créer des tables de fonctions. Nous devrions commencer à y être habitués car depuis le début de cette leçon, nous utilisons une table que nous avons appelée p (mais qui pourrait s'appeler autrement) dans laquelle, nous rangeons nos fonctions. Dans ce paragraphe, nous allons essayer de bien clarifier cette notion que nous utilisons, peut-être, mécaniquement sans trop bien comprendre ce que nous faisons. Pour cela nous allons utiliser des exemples.
Nous attirons l'attention du lecteur sur le fait que les exemples qui suivent (comme la plupart des exemples de cette leçon) pourront paraître totalement loufoques au programmeur de formation. Ils ont uniquement pour but de bien faire assimiler la notions de tables de fonctions aux étudiants et n'ont, par contre, aucune valeur d'exemple sur la manière de résoudre un problème concret. |
Exemple1
Nous allons écrire dans un Module:Ajout, trois fonctions f,g,h ayant pour but d'ajouter respectivement 1, 2, 3 à son argument. Nous écrirons ensuite une fonction p.ajoute qui, dans un premier temps, va ranger les trois fonctions f, g, h dans une table et ensuite, dans un deuxième temps, va incrémenter son premier argument, de la valeur indiquée par son deuxième argument, en utilisant les fonctions rangées dans la table :
local p = {}
function f(x)
return x+1
end
function g(x)
return x+2
end
function h(x)
return x+3
end
function p.ajoute(frame)
local Aj = {}
local reponse = tonumber(frame.args[1])
Aj.ajoute1 = f
Aj.ajoute2 = g
Aj.ajoute3 = h
if frame.args[2] == "1" then reponse = Aj.ajoute1(reponse) end
if frame.args[2] == "2" then reponse = Aj.ajoute2(reponse) end
if frame.args[2] == "3" then reponse = Aj.ajoute3(reponse) end
return reponse
end
return p
En écrivant : {{#invoke:Ajout|ajoute|17|2}}, nous obtenons : 19
Exemple2
Nous avons vu, au début de ce chapitre, que la notation Aj.ajoute1 utilisée pour les tables était une façon de noter, plus simplement, un accès à une table indexée par des chaînes de caractères et qui se noterait plus logiquement Aj["ajoute1"]. Nous allons donc, à titre d'exemple 2, rajouter, dans le Module:Ajout, une fonction p.rajoute qui est l'exacte réplique de la fonction p.ajoute mais utilisant l'autre notation pour l'accès a la table de fonctions Aj
function p.rajoute(frame)
local Aj = {}
local reponse = tonumber(frame.args[1])
Aj["ajoute1"] = f
Aj["ajoute2"] = g
Aj["ajoute3"] = h
if frame.args[2] == "1" then reponse = Aj["ajoute1"](reponse) end
if frame.args[2] == "2" then reponse = Aj["ajoute2"](reponse) end
if frame.args[2] == "3" then reponse = Aj["ajoute3"](reponse) end
return reponse
end
En écrivant : {{#invoke:Ajout|rajoute|23|1}}, nous obtenons : 24
Nous voyons donc que la notation Aj.ajoute1 est bien équivalente à la notation Aj["ajoute1"]
Pourquoi avons nous pris la peine de donner ces deux exemples identiques, à la notation de l'accès à la table près. C'est pour que l'étudiant prenne bien conscience que, dans la notation Aj.ajoute1, nous n'avons pas une fonction qui s'appellerait ajoute1 et qui serait placée dans la table Aj (comme certains pourraient le croire) mais nous avons une table de fonctions indexée par des chaînes de caractères. ajoute1 représente la chaîne de caractère "ajoute1" qui sert d'index d'accès à une fonction se trouvant dans la table Aj.
À l'appui de ce que nous venons de dire, on pourrait souligner le fait que puisque ajoute1 représente une chaîne de caractères et pas une fonction, nous aurions pu créer à part une vraie fonction ajoute1 et il n'y aurait pas eu de conflit. C'est ce que l’on aurait pu faire un peu plus haut lorsque nous avons donné l'exemple de la fonction factorielle comme fonction récursive. Nous avions écrit pour éviter d'embrouiller les esprit :
local p = {}
function fact(n)
if n == 0 then
return 1 -- on renvoie la valeur 1 quand le paramètre vaut 0
else
return n * fact(n - 1)
end
end
function p.factorielle(frame)
return fact(frame.args[1])
end
return p
Mais nous aurions pu écrire :
local p = {}
function factorielle(n)
if n == 0 then
return 1 -- on renvoie la valeur 1 quand le paramètre vaut 0
else
return n * factorielle(n - 1)
end
end
function p.factorielle(frame)
return fact(frame.args[1])
end
return p
et cela aurait tout aussi bien fonctionné.
Exemple3
La notation Aj.ajoute1 semble plus simple que la notation Aj["ajoute1"]. Nous allons donc dans cet exemple 3, mettre en évidence un intérêt, que peut avoir la dernière notation, en simplifiant l'exemple 2.
Dans le Module:Ajout, nous rajouterons la fonction p.incremente qui est une simplification de la fonction p.rajoute mettant à profit le fait que l’on a accès à l'index sous forme de chaîne de caractères. Nous écrirons :
function p.incremente(frame)
local Aj = {}
local index = "ajoute"..frame.args[2]
local reponse = tonumber(frame.args[1])
Aj["ajoute1"] = f
Aj["ajoute2"] = g
Aj["ajoute3"] = h
reponse = Aj[index](reponse)
return reponse
end
En écrivant : {{#invoke:Ajout|incremente|47|3}}, nous obtenons : 50
Fonctions ayant une table pour argument
[modifier | modifier le wikicode]Une fonction peut recevoir en argument tout type d'objet. Par conséquent, une fonction peut recevoir une table en argument. Il y a toutefois une petite différence dans le passage d'une table en argument dans une fonction. Les tables sont passés par référence alors que la plupart des autres variables sont passées par valeur. Nous allons nous efforcer de bien comprendre ce que cela signifie dans la suite de ce paragraphe.
Lorsqu'on passe un objet par valeur à une fonction, cela signifie que la fonction recopie l’objet dans la variable déclarée en argument dans la fonction. par exemple reprenons la fonction f défini plus haut :
function f(x)
return x^2
end
Si dans une autre fonction, on écrit
resultat = 7+f(a)
À l'appel de la fonction f par f(a), la valeur de a est recopié dans la variable x définie dans la fonction f et c’est x qui va être élevée au carré et pas la variable a.
Par contre, lorsqu'on passe un objet par référence à une fonction, cela signifie que la fonction reçoit l'adresse de l’objet qui lui est passé et pas sa valeur. Par conséquent la fonction va agir directement sur l’objet du programme appelant et pas sur une recopie de l'objet.
Dans le Lua, les tables sont des variables passées par référence. Cela peut se comprendre dans la mesure où les tables peuvent être des objets énormes puisque pouvant contenir un grand nombre d'objets. Si l’on devait recopier la table dans la fonction à chaque appel de fonction, la perte de temps ainsi que l'occupation mémoire serait trop importante.
Nous allons prendre deux exemples pour mettre en évidence la différence entre le passage par valeur et le passage par référence :
Dans un Module:Passage, nous écrirons deux fonctions p.valeur et p.reference. La première, appelant une fonction val auquel elle passe une variable par valeur (ici un nombre). La deuxième appelant une autre fonction ref auquel elle passe une variable par référence (ici une table). Chacune des deux fonctions va ensuite modifier l’objet passé. Nous vérifierons ensuite, dans le programme appelant, si la modification s'est aussi répercuté sur l’objet passé :
local p = {}
function val(x) -- x est sensé être un nombre
x = x + 3 -- On essaye d'incrémenter de 3 le contenu de x
end
function ref(x) -- x est sensé être une table
x[1] = x[1] + 3 -- On essaye d'incrémenter de 3 la première valeur de la table
end
function p.valeur(frame)
local a = tonumber(frame.args[1]) -- a est déclaré comme nombre et est initialisé avec la valeur de l'argument
val(a) -- appel de la fonction, ici a contient un nombre
return a -- On retourne le contenu de a pour voir s'il a été modifié
end
function p.reference(frame)
local a = {tonumber(frame.args[1])} -- a est déclaré comme table et est initialisé avec la valeur de l'argument en a[1]
ref(a) -- appel de la fonction, ici a contient une table
return a[1] -- On retourne le contenu de a pour voir s'il a été modifié
end
return p
En tapant {{#invoke:Passage|valeur|37}}, nous obtenons : 37. Nous voyons que l'argument n'a pas été modifié.
En tapant {{#invoke:Passage|reference|37}}, nous obtenons : 40. Nous voyons que l'argument a été incrémenté de 3.
Visibilité d'une variable
[modifier | modifier le wikicode]Nous avons, jusqu'à présent toujours déclaré les variables locales en début de fonction ou en début de bloc. La question qui se pose ici est de savoir ce qui se passe si la variable n’est pas déclarée localement en début de bloc, mais au milieu par exemple. Que les utilisateurs soient d'ores et déjà rassurés, cela ne provoque pas une erreur de script. En fait, une variable ne pourra être utilisée localement qu’à partir du moment où elle est déclarée comme locale. Si on tente d’utiliser ou de modifier une variable avant de la déclarer, on utilisera ou l’on modifiera une autre variable de même nom, celle-ci étant globale ou éventuellement déclarée locale en début de module.
Structures de contrôle
Les structures de contrôle permettent :
- soit de prendre une décision en fonction du contexte ;
- soit de répéter une opération sur des objets différents ;
- soit de répéter une opération tant que quelque chose est vrai ou jusqu'à ce que quelque chose soit réalisé.
Celles qui comportent une répétition sont appelées « boucles ».
La structure if..then..else
[modifier | modifier le wikicode]Nous avons déjà étudié cette structure au premier chapitre car il est difficile de s'en passer puisqu'elle permet de prendre une décision en fonction d'une condition. Nous rappelons que, en français la structure if condition then instruction1 else instruction2 end signifie : Si une condition est remplie exécuter instruction1 sinon exécuter instruction2. Dans le Module:Balance, nous avons donné l'exemple suivant :
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 !
Nous rajouterons, dans ce chapitre, qu’il est possible aussi d'emboîter les structures if..then..else. Par exemple, toujours dans le Module:Balance, nous avons écrit une quatrième fonction p.alerte4 ainsi :
local p = {}
function p.alerte4(frame)
local poids = tonumber(frame.args[1])
local reponse
if poids < 55 then
reponse = "Votre poids est acceptable"
else
if poids < 60 then
reponse = "Attention, vous commencez à grossir !"
else
reponse = "Grosse vache !!"
end
end
return reponse
end
return p
Qui présente l'avantage de fournir à l'utilisatrice une information plus complète[1].
Si l’on souhaite emboîter un très grand nombre de structures if..then..else, on peut le faire plus simplement en utilisant l'instruction elseif. Voir la fonction exemple, ci-dessous, qui nous confirme le nombre, rentré en argument, si celui-ci est compris entre 1 et 5 ou répond « Je ne sais pas » dans les autres cas :
local p = {}
function p.exemple(frame)
local n = tonumber(frame.args[1])
if n == 1 then
return "Le nombre est un"
elseif n == 2 then
return "Le nombre est deux"
elseif n == 3 then
return "Le nombre est trois"
elseif n == 4 then
return "Le nombre est quatre"
elseif n == 5 then
return "Le nombre est cinq"
else
return "Je ne sais pas"
end
end
return p
La structure while..do
[modifier | modifier le wikicode]La structure while..do permet de répéter un ensemble d'instructions tant qu'une condition est vraie. Sa syntaxe générale est : while condition do instructions end, ce qui signifie, en français : Tant que condition faire instructions fin.
Prenons un exemple : écrivons un programme qui va écrire le plus petit nombre, supérieur à un nombre donné, s'écrivant comme somme des premiers nombres entiers. Par exemple le plus petit nombre, supérieur à 13, vérifiant cette condition, est 15 car 1 + 2 + 3 + 4 = 10 et 1 + 2 + 3 + 4 + 5 = 15. Dans le Module:Calcul nous écrirons :
local p = {}
function p.somme1(frame)
local limite = tonumber(frame.args[1])
local reponse = 0
local entier = 1
while reponse < limite do
reponse = reponse + entier
entier = entier + 1
end
return reponse
end
return p
Dans une autre page, si nous écrivons {{#invoke:Calcul|somme1|13}}, nous obtenons : 15
Dans cet exemple, nous voyons que la variable limite a pris la valeur 13. Et tant que reponse avait une valeur inférieure à 13, reponse a été incrémentée de la valeur se trouvant dans entier. Comme entier valait 1 au départ et a été incrémentée de 1 dans chaque passage dans la boucle, nous voyons que reponse a pris la valeur 1 au premier passage dans la boucle, puis a pris la valeur 1 + 2 = 3 au deuxième passage, puis la valeur 1 + 2 + 3 = 6 au troisième passage, puis 1 + 2 + 3 + 4 = 10 au quatrième passage, puis 1 + 2 + 3 + 4 + 5 = 15 au cinquième passage. À ce moment-là, la condition reponse < limite est devenue fausse et par conséquent nous sommes sortis de la boucle avec reponse contenant la valeur 15.
La structure repeat..until
[modifier | modifier le wikicode]La structure : repeat..until permet de répéter un ensemble d'instructions jusqu'à ce qu'une condition soit vraie. Sa syntaxe générale est : repeat instructions until condition, ce qui signifie, en français : Répéter instructions jusqu'à condition.
Si nous essayons de réaliser une fonction réalisant la même chose qu'au paragraphe précédent, nous obtiendrons :
local p = {}
function p.somme2(frame)
local limite = tonumber(frame.args[1])
local reponse = 0
local entier = 1
repeat
reponse = reponse + entier
entier = entier + 1
until reponse > limite
return reponse
end
return p
Dans une autre page, si nous écrivons {{#invoke:Calcul|somme2|13}}, nous obtenons : 15
Dans ce nouvel exemple, nous voyons que la variable limite a pris la valeur 13. reponse a été incrémentée de la valeur se trouvant dans entier qui, comme dans l'exemple précédent, est incrémentée de 1 à chaque passage dans la boucle, ces deux opérations se renouvelant jusqu'à ce que reponse prenne une valeur supérieure à 13. Nous voyons que reponse a pris la valeur 1 au premier passage dans la boucle, puis a pris la valeur 1 + 2 = 3 au deuxième passage, puis la valeur 1 + 2 + 3 = 6 au troisième passage, puis 1 + 2 + 3 + 4 = 10 au quatrième passage, puis 1 + 2 + 3 + 4 + 5 = 15 au cinquième passage. À ce moment-là, la condition reponse > limite est devenue vraie et, par conséquent, nous sommes sortis de la boucle avec reponse contenant la valeur 15.
Ce qui différencie ce deuxième exemple du premier, c’est que l'exécution de la boucle aura lieu au moins une fois et reponse prendra donc au minimum la valeur 1. Si nous tapons {{#invoke:Calcul|somme2|0}}, nous obtiendrons 1 alors que dans l'exemple précédent {{#invoke:Calcul|somme1|0}} aurait donné 0.
Nous pouvons dire que, dans ce cas, l'exemple précédent est meilleur puisque donnant une réponse juste pour la valeur 0.
En général, tout ce qui est réalisable avec la structure while..do est aussi réalisable avec la structure repeat..until. La principale différence réside dans le fait que, dans la structure while..do, la condition est testée avant chaque exécution des instructions et dans la structure repeat..until, la condition est testé après chaque exécution des instructions. Ce qui, selon ce que l’on veut faire, va déterminer le choix entre les deux structures.
La structure for..do
[modifier | modifier le wikicode]Nous avons vu, dans les deux paragraphes précédents, des structures où le nombre de passage dans la boucle n’est pas connu d'avance et dépend d'une condition qui doit rester vraie ou qui doit devenir vraie. Si l’on veut répéter une opération un certain nombre de fois bien précis connu avant l'entrée dans la boucle, on utilisera la structure for..do. La seule petite différence entre chaque exécution sera déterminée par une variable d'index qui pour chaque exécution prendra une valeur différente et qui pourra être éventuellement utilisée dans le corps de la boucle.
Dans le Lua, il existe deux formes de la boucle for.
Première forme de la boucle for
[modifier | modifier le wikicode]La première forme de la boucle for se rédige sous la forme :
for index = m, n, p do
instructions
end
m, n, p étant trois entiers. p est facultatif. Pour chaque passage dans la boucle, la variable index prendra toutes les valeurs de m à n en étant incrémentée, chaque fois, de la valeur de p.
Prenons des exemples.
Écrivons, toujours dans notre Module:Calcul, une fonction echo qui répétera un mot un certain nombre de fois, lui aussi entré en paramètre. Nous écrirons :
local p = {}
function p.echo(frame)
local nom = frame.args[1]
local occurence = tonumber(frame.args[2])
local reponse = " "
for i = 1, occurence do
reponse = reponse.." "..nom
end
return reponse
end
return p
Dans une autre page, si nous écrivons {{#invoke:Calcul|echo|plouf|5}}, nous obtenons : plouf plouf plouf plouf plouf
Voyons, plus en détail, comment fonctionne cette boucle. Dans notre exemple, occurence prend la valeur 5. Par conséquent, notre boucle devient :
for i = 1, 5 do
reponse = reponse.." "..nom
end
i = 1, 5 signifie que i doit prendre toutes les valeurs de 1 à 5 et la boucle s'exécutera pour chacune de ces valeurs. Nous aurons donc obligatoirement 5 passages dans la boucle. En français, on pourrait traduire cette exécution par : Pour i prenant toutes les valeurs de 1 à 5 faire instructions.
On peut faire plus sophistiqué en mettant un troisième nombre qui représentera de combien i doit être augmenté à chaque passage dans la boucle. Par exemple : i = 2, 11, 3 signifie que i, en démarrant de 2 devra aller jusqu'à 11 en étant augmenté de 3 à chaque passage dans la boucle. Par conséquent, nous aurons 4 passages dans la boucle avec i ayant, à chaque passage, respectivement les valeurs 2, 5, 8, 11.
Il est possible aussi de faire en sorte que i soit décrémenté à chaque passage dans la boucle. Par exemple, si l’on écrit : i = 17, 9, -2. Cela signifie que i va aller de 17 à 9 en étant décrémenté de 2 à chaque passage dans la boucle. i va donc prendre à chaque passage dans la boucle respectivement les valeurs 17, 15, 13, 11 et 9 et l’on aura donc 5 passages dans la boucle.
La boucle for..do est très pratique pour manipuler des tables. Nous allons faire un programme qui fait automatiquement pour tous les jours de la semaine ce que le programme du premier chapitre faisait pour un seul jour. Dans le Module:Traduit, nous rajoutons la fonction baratin2 ainsi rédigée :
local p = {}
local semaine = {"lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"}
local week = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}
function p.baratin2(frame)
local reponse = "<u>Traduction des jours de la semaine</u> <br />"
for index = 1, 7 do
reponse = reponse.."La traduction de "..semaine[index]..", en anglais, est "..week[index].."<br />"
end
return reponse
end
return p
En écrivant : {{#invoke:Traduit|baratin2}}, nous obtenons :
Traduction des jours de la semaine
La traduction de lundi, en anglais, est monday
La traduction de mardi, en anglais, est tuesday
La traduction de mercredi, en anglais, est wednesday
La traduction de jeudi, en anglais, est thursday
La traduction de vendredi, en anglais, est friday
La traduction de samedi, en anglais, est saturday
La traduction de dimanche, en anglais, est sunday
Nous remarquons que le wikicode est accepté puisque nous avons réussi à aller à la ligne avec <br />
et nous avons réussi à souligner le titre avec les balises <u></u>
. Dans ce programme, nous avons commencé par déclarer la variable reponse en l'initialisant avec le titre. Nous avons ensuite utilisé une boucle for..do avec une variable d'index allant de 1 à 7. Cette variable d'index servant à accéder aux différentes cases du tableau. La boucle for..do s’avère donc être un excellent moyen pour manipuler la totalité des données se trouvant dans une table.
Deuxième forme de la boucle for
[modifier | modifier le wikicode]Cette deuxième forme de la boucle for appelée aussi forme itérative de la boucle for est plus particulièrement adaptée aux tables. Elle se rédige sous la forme :
for clé, objet in fonction itérative, table, arrêt do
instructions
end
Durant le parcours de la table, clé prend la valeur de la clé considérée et objet prend la valeur correspondante. fonction itérative est une fonction gérant le parcourt de la table, elle gère le parcourt des clés qui nous intéresses dans l’ordre qui nous intéresse. table est la table considérée et arrêt est la valeur qui stoppe le parcourt de la table, le plus souvent nil.
Exemple concernant les tables à clé numérique
Dans le Module:Iteratif, nous écrirons la fonction p.description accompagnée de la fonction suivant ainsi :
local souk = {"flute", "pipo", "manche à balaie", "serpière", "jeu de cartes", "coton tige", "tourne vis", "rateau", "stylo", "poupée"}
function suivant(tab,n)
if n == nil then n = 0 end
if tab[n+1] == nil then
return nil,nil
else
return n+1,tab[n+1]
end
end
function p.description()
local reponse = " "
for index, objet in suivant,souk,nil do
reponse = reponse.."<br />à la clé numéro "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
La fonction suivant est la fonction itérative qui gère le parcours de la table. Cette fonction retourne deux valeurs : la valeur de la clé suivante et l’objet correspondant. Ici, on se contente d'augmenter la clé d'une unité à chaque boucle. Cette fonction a deux entrées, la table considérée et la valeur de la clé précédente à partir de laquelle elle devra calculer la clé suivante. Au début, la valeur de la clé précédente n'existe pas et la valeur nil est fournie à la place. La fonction suivant voyant nil comme clé précédente devra fournir, comme clé suivante, la valeur de la première clé à considérer. Lorsque le parcours de la table sera achevé, la variable lue dans la table sera nil et la fonction retournera donc nil comme valeur de la clé suivante indiquant ainsi à la boucle for que le parcours de la table est terminé.
{{#invoke:iteratif|description}} nous retourne :
à la clé numéro 1 se trouve l’objet flute.
à la clé numéro 2 se trouve l’objet pipo.
à la clé numéro 3 se trouve l’objet manche à balaie.
à la clé numéro 4 se trouve l’objet serpière.
à la clé numéro 5 se trouve l’objet jeu de cartes.
à la clé numéro 6 se trouve l’objet coton tige.
à la clé numéro 7 se trouve l’objet tourne vis.
à la clé numéro 8 se trouve l’objet rateau.
à la clé numéro 9 se trouve l’objet stylo.
à la clé numéro 10 se trouve l’objet poupée.
Dans la pratique, le plus souvent, le parcours de la table commence à la clé 1 et s'opère ainsi en incrémentant d'une unité la valeur de la clé jusqu'à atteindre la clé de valeur la plus élevée comme dans l'exemple précédent. Par conséquent, pour simplifier, le Lua fournit, dans ce cas particulier, une fonction préprogrammée nommée ipairs qui retourne trois valeurs en sortie, à savoir la fonction itérative, la table, et la valeur nil. Grâce à la fonction ipairs, l'exemple précédent se simplifie ainsi :
local souk = {"flute", "pipo", "manche à balaie", "serpière", "jeu de cartes", "coton tige", "tourne vis", "rateau", "stylo", "poupée"}
function p.description()
local reponse = " "
for index, objet in ipairs(souk) do
reponse = reponse.."<br />à la clé numéro "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
Nous étudierons de façon plus approfondie la fonction ipairs dans le chapitre sur les fonctions basiques.
Exemple concernant les tables à clé quelconque
Nous avons vu, dans l'exemple précédent, comment parcourir une table à clé numérique en utilisant la deuxième forme de la boucle for. Supposons que nous voulions faire de même avec une table avec clé sous forme de chaîne de caractères. Le problème qui se pose est de savoir comment écrire la fonction itérative que nous avons appelée suivant dans l'exemple précédent. Comment passer d'une clé sous forme de chaîne de caractère à la suivante sans en oublier. Nous voyons que le problème n’est pas facile à résoudre. Pour nous faciliter la chose, le Lua fournit une fonction préprogrammée appelé next qui joue exactement le même rôle que la fonction suivant de l'exemple précédent, mais pour les clés sous forme de chaîne de caractères.
Prenons un exemple :
local p = {}
local fouillis = {["Nourriture"] = "Fromage", ["Boisson"] = "Limonade", ["Bestiole"] = "Cafard", ["Couvert"] = "Fourchette", ["Truc"] = "Machin chose"}
function p.farfouille()
local reponse = " "
for index, objet in next,fouillis,nil do
reponse = reponse.."<br />à la clé "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
return p
{{#invoke:iteratif|farfouille}} nous retourne :
à la clé Nourriture se trouve l’objet Fromage.
à la clé Boisson se trouve l’objet Limonade.
à la clé Couvert se trouve l’objet Fourchette.
à la clé Truc se trouve l’objet Machin chose.
à la clé Bestiole se trouve l’objet Cafard.
Comme pour les tables à clé numérique, le Lua nous facilite un peu les choses en fournissant aussi une fonction préprogrammée appelée pairs qui retourne trois valeurs : une fonction itérative, la table considérée, la valeur nil. Nous pouvons donc simplifier un peu le programme précédent en l'écrivant :
local p = {}
local fouillis = {["Nourriture"] = "Fromage", ["Boisson"] = "Limonade", ["Bestiole"] = "Cafard", ["Couvert"] = "Fourchette", ["Truc"] = "Machin chose"}
function p.farfouille()
local reponse = " "
for index, objet in pairs(fouillis) do
reponse = reponse.."<br />à la clé "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
return p
Cette fois, nous n'avons pas gagné grand-chose !
Opérateurs logiques
[modifier | modifier le wikicode]Nous avons vu que certaines structures de contrôle dépendent d'une condition. Nous allons, dans ce paragraphe, étudier plus en détail la formulation de la condition.
Nous avons, jusqu'à maintenant, principalement vu que les variables pouvaient contenir deux types de données que l’on a appelés chaîne de caractères et nombre. Nous allons voir maintenant un nouveau type de donnée que l’on appelle booléen. Une variable de type booléen peut être affectée de deux façons, soit avec la valeur false, soit avec la valeur true. Si elle mémorise la valeur false, cela signifie qu'elle mémorise que quelque chose est faux. Si elle mémorise la valeur true, cela signifie qu'elle mémorise que quelque chose est vrai.
Pour indiquer qu'une variable est de type booléen, on peut l'initialiser avec les valeurs true ou false
Par exemple :
local correct = true
local panne = false
true et false sont des mots réservés du langage Lua.
Il est possible aussi de les initialiser avec quelque chose de vrai ou de faux :
local correct = 2 > 1
local panne = 2 < 1
Dans l'exemple ci-dessus, après initialisation, correct contiendra true et panne contiendra false
Les variables en Lua, peuvent devenir de type booléen après affectation, même si elles sont initialisées d'un autre type.
Par exemple, dans le Module:Logique, nous écrirons la fonction essai1 ainsi :
local p = {}
function p.essai1()
local reponse = "Coucou, c’est moi !"
reponse = 2 > 1
return reponse
end
return p
En tapant : {{#invoke:Logique|essai1}}, nous obtenons : true
La variable reponse, qui était de type chaîne de caractère, est devenue de type booléen après affectation de 2 > 1 et a pris la valeur true car 2 est bien supérieur à 1
N'oublions pas que nous sommes dans le chapitre sur les structures de contrôle. Dans une structure de contrôle : if..then..else, while..do ou repeat..until, nous pouvons nous servir des variables de type booléen comme condition. Si la variable contient le booléen true, la condition sera considérée comme vraie. Si la variable contient le booléen false, la condition sera considérée comme fausse.
Par exemple, toujours dans le Module:Logique, écrivons une fonction essai2 ainsi :
local p = {}
function p.essai2()
local condition = false
if condition then
return "La condition est vraie"
else
return "La condition est fausse"
end
end
return p
En tapant : {{#invoke:Logique|essai2}}, nous obtenons : La condition est fausse
Il est possible de se servir d'autres variables ou valeurs comme condition dans les tests et boucles. Il suffit de savoir que toutes les variables ou valeurs différentes de nil sont assimilables à true. Seule une variable contenant nil ou une valeur égale à nil est assimilable à false
Par exemple, toujours dans le Module:Logique, écrivons une fonction essai4 ainsi :
local p = {}
function p.essai4()
local condition = 0
if condition then
return "La condition est vraie"
else
return "La condition est fausse"
end
end
return p
En tapant : {{#invoke:Logique|essai4}}, nous obtenons : La condition est vraie
Nous remarquons que 0 a été assimilé à true contrairement à d’autre langage, comme le langage C, où 0 est assimilé à false. Ceci procède d'une certaine logique car, en langage C, une fonction qui se déroule mal retourne, en principe, 0 pour indiquer que cela s'est mal passé alors qu'en Lua, si cela se passe mal, la fonction retourne nil. Nil n'existe pas en langage C, son équivalent est 0.
Autre exemple : toujours dans le Module:Logique, écrivons une fonction essai5 ainsi :
local p = {}
function p.essai5()
local condition
if condition then
return "La condition est vraie"
else
return "La condition est fausse"
end
end
return p
En tapant : {{#invoke:Logique|essai5}}, nous obtenons : La condition est fausse
la variable condition n'a pas été assignée, donc contient nil, et nous voyons que nil est assimilé à false.
De même que les variables de type nombre peuvent être combinées avec les opérateurs +, -, *, /, ^,
de même que les variables de type chaîne de caractères peuvent être combinées avec l'opérateur ..,
les variables de type booléen peuvent être combiné avec les opérateurs and, or, not.
L'opérateur and
[modifier | modifier le wikicode]Soit a et b deux variables booléennes et soit l'affectation :
c = a and b
alors c sera vraie uniquement si les deux variables a et b sont toutes les deux vraies. Plus précisément, on peut faire un tableau, appelé table de vérité, donnant la valeur de c en fonction des valeurs de a et b, ainsi :
a | b | c |
false | false | false |
false | true | false |
true | false | false |
true | true | true |
L'opérateur or
[modifier | modifier le wikicode]Soit a et b deux variables booléennes et soit l'affectation :
c = a or b
alors c sera vraie si au moins l'une des deux variables a ou b est vraie. Plus précisément, on peut faire un tableau, appelé table de vérité, donnant la valeur de c en fonction des valeurs de a et b, ainsi :
a | b | c |
false | false | false |
false | true | true |
true | false | true |
true | true | true |
L'opérateur not
[modifier | modifier le wikicode]Soit a une variable booléenne et soit l'affectation :
c = not a
alors c sera vraie si a est fausse et sera fausse si a est vraie. Plus précisément, on peut faire un tableau, appelé table de vérité, donnant la valeur de c en fonction de la valeur de a, ainsi :
a | c |
false | true |
true | false |
Il est, bien sûr, possible d'opérer directement sur les conditions sans passer par des variables. Toujours dans le Module:Logique, considérons la fonction essai3 qui nous indique si l'argument entré est strictement compris entre 1 et 8 :
local p = {}
function p.essai3(frame)
local n = tonumber(frame.args[1])
if 1 < n and n < 8 then
return "Le nombre est strictement compris entre 1 et 8"
else
return "Le nombre n’est pas strictement compris entre 1 et 8"
end
end
return p
Si nous écrivons : {{#invoke:Logique|essai3|6}}, nous obtenons : Le nombre est strictement compris entre 1 et 8
Par contre, si nous écrivons :
local p = {}
function p.essai3(frame)
local n = tonumber(frame.args[1])
if 1 < n < 8 then
return "Le nombre est strictement compris entre 1 et 8"
else
return "Le nombre n’est pas strictement compris entre 1 et 8"
end
end
return p
nous obtenons une erreur de script !
Opérateurs externes
[modifier | modifier le wikicode]Nous dirons qu'un opérateur est externe si le résultat de l'opération est d'un type différent de celui des objets sur lesquels s'effectue l'opération
Par exemple, nous avons écrit plus haut :
local correct = 2 > 1
local panne = 2 < 1
Ce qui nous montre que > et < sont des opérateurs externes car il effectue une opération entre les nombres 1 et 2 et le résultat de cette opération est un booléen qui sera affecté aux variables correct ou panne.
> est l'opérateur "strictement supérieur". Nous avons aussi l'opérateur "supérieur ou égal" qui se noterait >=.
< est l'opérateur "strictement inférieur". Nous avons aussi l'opérateur "inférieur ou égal" qui se noterait <=.
Nous avons aussi déjà utilisé l'opérateur == qui permet de comparer deux variables et qui retourne "true" si les variables sont égales ou "false" si les variables sont différentes. Nous avons aussi l'opérateur ~= qui compare aussi deux variables mais retourne "false" si les variables sont égales et "true" si les variables sont différentes.
Références
[modifier | modifier le wikicode]- ↑ Si l'utilisatrice est en fait un utilisateur homme, il faut remplacer « Grosse vache ! » par « Gros patapouf ! ». Notons cependant que cela ne change rien à la logique de la programmation.
Fonctions basiques
Au paragraphe précédent, nous avons étudié les fonctions. Si nous créons une fonction, c’est pour effectuer généralement un travail répétitif. Plutôt que de répéter une même séquence d'instructions dans plusieurs parties d'un programme, nous créons une fonction contenant cette séquence et nous utilisons cette fonction en différentes parties du programme. Sur ce principe, le Lua met à la disposition des utilisateurs un certain nombre de fonctions pré-créées. Les concepteurs du Lua ont remarqué que certains besoins se faisaient souvent sentir dans l'écriture d'un programme et ont donc préprogrammé un certain nombre de fonctions que nous allons commencer à étudier dans ce chapitre. Dans ce chapitre, nous verrons certaines des fonctions préprogrammées les plus basiques, qui ne sont pas spécialisées dans un domaine particulier. Dans les chapitres suivants, nous verrons des fonctions plus spécialisées dans un domaine particulier comme les mathématiques ou les chaînes de caractères.
L'ordre dans lequel nous étudierons les différentes fonctions ne sera pas alphabétique. Nous essaierons d'adopter un ordre pédagogique en étudiant les fonctions dans l’ordre de difficulté et aussi pratique en étudiant d’abord les fonctions utiles pour mieux comprendre les suivantes.
Tous les exemples de ce chapitre se trouve dans le Module:Basique
type
[modifier | modifier le wikicode]La fonction type permet de connaître le type d'objet que contient une variable en retournant l'une des chaîne de caractères suivantes : "nil", "number", "string", "boolean", "table", et "function". Nous avons étudier chacun de ces types dans les chapitres précédent :
- "nil" : c’est en quelque sorte ce que contient une variable par défaut si elle n’est pas affectée. C'est aussi ce que retourne certaines fonctions lorsque quelque chose s'est mal passé.
- "number" : indique que la variable contient un nombre.
- "string" : indique que la variable contient une chaîne de caractères.
- "boolean" : indique que la variable contient un booléen : true ou false (vraie ou faux).
- "table" : indique que la variable contient une table.
- "function" : indique que la variable contient une fonction.
À titre d'exemple, dans un Module:Basique, nous écrirons une fonction toutype qui retourne tous les types possibles en fabriquant une variable qui contiendra successivement tous les types possibles et qui sera, à chaque fois, testée par la fonction type.
local p = {}
function f(x)
return x^2
end
function p.toutype()
local reponse = " "
reponse = reponse..type(cameleon).." " -- La variable cameleon n'a pas été affectée donc la fonction type devrait retourner "nil"
cameleon = 7
reponse = reponse..type(cameleon).." "
cameleon = "miam miam"
reponse = reponse..type(cameleon).." "
cameleon = true
reponse = reponse..type(cameleon).." "
cameleon = p -- N'oublions pas que p a été déclaré comme étant une table en début de module
reponse = reponse..type(cameleon).." "
cameleon = f -- f est la fonction défini un peu plus haut et qui élève un nombre au carré
reponse = reponse..type(cameleon)
return reponse
end
return p
{{#invoke:Basique|toutype}} nous retourne : nil number string boolean table function
tonumber
[modifier | modifier le wikicode]Nous connaissons déjà, un peu, cette fonction, car nous avons dû l'introduire dès le premier chapitre, compte tenu de son utilité. Cette fonction tente de convertir une chaîne de caractères en nombre. Par exemple, elle convertira la chaîne de caractères "23" en nombre 23. S'il n’est pas possible de convertir la chaîne de caractère en nombre, la fonction retournera nil.
À titre d'exemple, dans le Module:Basique, nous écrirons une fonction nombre qui indique à l'utilisateur si l'argument rentré est une chaîne convertible en nombre.
local p = {}
function p.nombre(frame)
local n = tonumber(frame.args[1])
if n == nil then
return "Je n'ai pas réussi à convertir votre chaine de caractères en nombre"
else
return "Vous avez bien rentré un nombre"
end
end
return p
{{#invoke:Basique|nombre|27}} nous retourne : Vous avez bien rentré un nombre
27 est bien un nombre !
{{#invoke:Basique|nombre|Maison}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre
Une maison n’est pas un nombre !
{{#invoke:Basique|nombre|vingt-sept}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre
La fonction tonumber ne connait pas le français !
{{#invoke:Basique|nombre|eleven}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre
Elle ne connait pas l'anglais non plus !
{{#invoke:Basique|nombre|3.14}} nous retourne : Vous avez bien rentré un nombre
La fonction tonumber reconnait le point décimal !
{{#invoke:Basique|nombre|3,14}} nous retourne : Je n'ai pas réussi à convertir votre chaine de caractères en nombre
Mais elle ne reconnait pas la virgule !
{{#invoke:Basique|nombre|7.5698e-3}} nous retourne : Vous avez bien rentré un nombre
Elle reconnait la notation scientifique !
tostring
[modifier | modifier le wikicode]La fonction tostring est un peu le contraire de la fonction tonumber, car elle convertit son argument en chaîne de caractères.
Comme exemple, nous allons reprendre l'exemple donné pour la fonction type, mais en remplaçant type par tostring :
local p = {}
function f(x)
return x^2
end
function p.verschaine()
local reponse = " "
reponse = reponse..tostring(cameleon).." " -- La variable cameleon n'a pas été affectée donc la fonction type devrait retourner "nil"
cameleon = 7
reponse = reponse..tostring(cameleon).." "
cameleon = "mian mian"
reponse = reponse..tostring(cameleon).." "
cameleon = true
reponse = reponse..tostring(cameleon).." "
cameleon = p -- N'oublions pas que p a été déclaré comme étant une table en début de module
reponse = reponse..tostring(cameleon).." "
cameleon = f -- f est la fonction défini un peu plus haut et qui élève un nombre au carré
reponse = reponse..tostring(cameleon)
return reponse
end
return p
{{#invoke:Basique|verschaine}} nous retourne : nil 7 mian mian true table function
Lorsque l’objet est nil, une table ou une fonction, la fonction tostring nous délivre le même message que la fonction type. Lorsque l’objet est une chaîne de caractères, tostring nous rend la chaîne à l'identique. Lorsque l’objet est un nombre, ce nombre est transformé en chaîne en rajoutant des guillemets (7 devient "7"). Lorsque l’objet est un booléen, tostring nous délivre "true" ou "false" selon le cas.
error
[modifier | modifier le wikicode]C'est une fonction qui provoque volontairement une erreur de script, mais avec un message d'erreur que l’on a choisi et un contrôle sur la position de l'erreur indiquée par le Lua.
La syntaxe de cette fonction est error(message, niveau). message est le message d'erreur apparaissant lorsque l’on clique sur l'erreur de script(en rouge).
Pour niveau :
S'il y a 0, on n'aura pas de message indiquant la ligne ou la fonction error se trouve.
S'il y a 1 ou rien, on aura un message indiquant la ligne ou la fonction error concernée se trouve.
S'il y a 2, on aura un message indiquant la ligne ou se fait l'appel à la fonction contenant la fonction error concernée.
S'il y a 3, on aura un message indiquant la ligne ou se fait l'appel à une fonction qui a appelé la fonction contenant la fonction error concernée.
Et ainsi de suite..
Prenons un exemple :
local p = {}
function i(x)
error("turlututu chapeau pointu",3)
end
function h(x)
i(x)
end
function g(x)
h(x)
end
function p.erreur()
g(2)
end
return p
{{#invoke:Basique|erreur}} nous retourne : Erreur Lua dans Module:Basique à la ligne 103 : turlututu chapeau pointu.
En cliquant sur l'erreur de script si dessus, nous obtenons :
Erreur Lua dans Module:Basique à la ligne n:
turlututu chapeau pointu.
Dans la pile des appels, nous voyons que la ligne n (voir la valeur de n en cliquant sur l'erreur de script ci-dessus) correspond à la ligne où a été déclarée la fonction g. la fonction g appelant la fonction h qui appelle la fonction i ou nous avons mis la fonction error, nous sommes bien en conformité avec le fait que nous avons mis 3 comme niveau de la fonction error.
pcall
[modifier | modifier le wikicode]La fonction pcall permet d'appeler une fonction en gérant une éventuelle erreur lors de son exécution.
Dans l'exemple ci-dessous, la fonction p.crash appelle avec pcall une fonction dans laquelle, on a mis volontairement une instruction qui, normalement devrait provoquer une erreur de script. Nous allons voir que nous n'avons pas le fameux message rouge indiquant l'erreur de script car la fonction pcall gère elle-même l'erreur de script. Le premier argument de la fonction pcall est le nom de la fonction appelée, les arguments suivants sont les arguments de la fonction appelée.
local p = {}
function plouf(x)
local tab ={6,8}
return x + tab
end
function p.crash()
local a,b = pcall(plouf,3)
if a == false then
return b
else
return "Aucun probléme ! La fonction a retourné"..b
end
end
return p
{{#invoke:Basique|crash}} nous retourne : Module:Basique:112: attempt to perform arithmetic on local 'tab' (a table value)
Dans le programme précédent, la fonction pcall retourne deux valeurs (si la fonction appelée retournait n valeurs, la fonction p.call retournerait n + 1 valeurs).
Dans notre exemple, la fonction p.call appelle la fonction plouf dans laquelle, nous avons mis volontairement une erreur de script (nous tentons l'addition d'un nombre avec une table). Au lieu d’avoir notre habituel message rouge indiquant l'erreur de script, la fonction pcall va retourner en première valeur (a dans notre exemple), la valeur false et en seconde valeur (b dans notre exemple) un message indiquant la ligne ou s'est produite l'erreur de script et la nature de l'erreur de script (voir l'exécution de notre exemple).
S'il n'y avait pas eut d'erreur de script dans la fonction plouf de notre exemple, la fonction p.call aurait retourné en première valeur, la valeur true et en seconde valeur la valeur retournée normalement par la fonction plouf. Si la fonction plouf retournait plusieurs valeurs, la fonction pcall retournerait aussi toutes ces valeurs à la suite de la valeur true.
Voir un autre exemple d'utilisation de la fonction pcall dans le chapitre : Gestion de l'environnement, paragraphe : Gestion des erreurs (troisième sous-paragraphe).
next
[modifier | modifier le wikicode]Nous avons vu que les clés des tables pouvaient aussi bien être des nombres que des chaînes de caractères. Dans d'autres langages, l'accès aux tables se fait uniquement avec des nombres et l’on à vu que si l’on souhaitait faire quelque chose sur une table indexée par des nombres, on pouvait le faire facilement en utilisant une boucle for..do. Là où les choses deviennent plus compliquées, c’est quand l’on souhaite travailler sur une table indexée par des chaînes de caractères. Le programmeur habitué, dans un autre langage, aux tables indexées par des nombres, a de quoi être décontenancé. Comment parcourir une table indexée par des chaînes de caractères. C'est ici, qu'intervient la fonction next. Cette fonction a la capacité de nous donner l'index suivant un index donné dans une table ainsi que l’objet correspondant à cet index (cette fonction retourne deux objets). Par exemple, si nous écrivons next(tab, "bouillotte"), tab étant une table et "bouillotte", une chaîne de caractères servant d'index dans la table tab, alors cette instruction nous donnera l'index suivant dans la table et l’objet correspondant ("marmite" par exemple et ce qu’il y a dans la marmitte). Il suffira alors d'écrire : next(tab, "marmite") pour avoir, à nouveau, l'index suivant avec l’objet correspondant et ainsi de suite. On pourra de cette façon parcourir toute la table !
Nous devons préciser deux petits détails :
Si nous ne savons pas quel est le premier index, nous taperons simplement next(tab, nil) et la fonction next nous retournera le premier index et l’objet correspondant. On peut aussi taper simplement next(tab) pour avoir le premier index et l’objet correspondant.
Si "bouillotte" est le dernier index de la table, alors next(tab, "bouillotte") nous retournera nil
Nous devons bien noter ces deux petits détails, car ils peuvent être causes de bien des soucis dans un programme.
À titre d'exemple, nous commencerons par écrire une fonction listecle qui se contentera de retourner tous les index d'une table que l’on aura rentrée avant la fonction ainsi que les objets correspondants:
local Categorie = { ["Prénom"] = "Christine", ["Mois"] = "Avril", ["Métier"] = "Boulanger", ["Poisson"] = "Truite", ["Métal"] = "Argent", ["Planète"] = "Saturne", ["Instrument"] = "Piano"}
function p.listecle()
local reponse = " "
local suivant, objet
for index = 1, 7 do
suivant,objet = next(Categorie,suivant)
reponse = reponse.."<br> "..suivant.." correspond à "..objet
end
return reponse
end
{{#invoke:Basique|listecle}} nous retourne :
Prénom correspond à Christine
Instrument correspond à Piano
Planète correspond à Saturne
Métal correspond à Argent
Mois correspond à Avril
Poisson correspond à Truite
Métier correspond à Boulanger
La première remarque que l’on peut faire est que les index ne sont pas sortis dans l’ordre où nous les avons écrit ??? Qu'importe, du moment où on les a tous !!! (C'est même pas l’ordre alphabétique d'ailleurs !?)
L'instruction intéressante dans ce programme est l'instruction :
suivant,objet = next(Categorie,suivant)
qui, à chaque boucle, remplace l'index contenu dans la variable suivant par l'index suivant et donne aussi l’objet correspondant.
Le programme précédent est un peu simpliste car il supposait que nous connaissions déjà le contenu de la table et qu’il y avait exactement 7 index. C'est pour cela que nous avons utilisé une boucle for..do. Si l’on remplace dans la programme précédent le 7 de la boucle for..do par un 8, alors nous aurons une belle erreur de script à cause de l'instruction :
reponse = reponse.."<br> "..suivant.." correspond à "..objet
car suivant contiendra nil à la huitième boucle
Si nous ne connaissons pas la table d'avance et que nous ne savons pas combien elle contient de clés, nous devons la parcourir avec une autre instruction de contrôle capable de détecter le nil apparaissant dans la variable suivant lorsque l’on a fini de parcourir la table.
nous allons donc écrire une fonction yatil, moins lamentable que la précédente, en utilisant une boucle repeat..until qui va parcourir la table jusqu'à ce que nil apparaisse dans suivant. Ce programme a pour but de nous indiquer si un mot rentré en paramètre se trouve dans la table :
local Categorie = { ["Prénom"] = "Christine", ["Mois"] = "Avril", ["Métier"] = "Boulanger", ["Poisson"] = "Truite", ["Métal"] = "Argent", ["Planète"] = "Saturne", ["Instrument"] = "Piano"}
function p.yatil(frame)
local suivant,objet
local trouve = false
repeat
suivant,objet = next(Categorie,suivant) --Cherche la clé suivante et la met dans suivant
if objet == frame.args[1] then trouve = true end
until suivant == nil or trouve -- On tourne dans la boucle jusqu'à ce que toute la table ait été parcourue ou jusqu'à ce qu'on ait trouvé
if trouve then
return "Le mot figure dans la table"
else
return "Le mot ne figure pas dans la table"
end
end
Nota : Le programme ci-dessus est volontairement mal écrit. Une amélioration de celui-ci est proposée dans l'exercice 9-2.
{{#invoke:Basique|yatil|Truite}} nous retourne : Le mot figure dans la table
{{#invoke:Basique|yatil|Trottinette}} nous retourne : Le mot ne figure pas dans la table
Astuce Nous avons vu que next(tab) nous fourni la première clé de la table. Si next(tab) nous retourne nil, cela signifie qu’il n'y a pas de première clé dans la table, autrement dit que la table est vide. next(tab) permet donc aussi de tester si une table est vide. |
ipairs
[modifier | modifier le wikicode]La fonction ipairs nous fourni une façon plus évolué et surtout plus commode de parcourir une table avec clé numérique. Un exemple simple vaut mieux qu'un long discours et va nous permettre de mieux comprendre. Écrivons donc une fonction description qui se contente de dire bêtement ce que l’on trouve à la clé numéro i (i étant un nombre) :
local souk = {"flute", "pipo", "manche à balaie", "serpière", "jeu de cartes", "coton tige", "tourne vis", "rateau", "stylo", "poupée"}
function p.description()
local reponse = " "
for index, objet in ipairs(souk) do
reponse = reponse.."<br>à la clé numéro "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
{{#invoke:Basique|description}} nous retourne :
à la clé numéro 1 se trouve l’objet flute.
à la clé numéro 2 se trouve l’objet pipo.
à la clé numéro 3 se trouve l’objet manche à balaie.
à la clé numéro 4 se trouve l’objet serpière.
à la clé numéro 5 se trouve l’objet jeu de cartes.
à la clé numéro 6 se trouve l’objet coton tige.
à la clé numéro 7 se trouve l’objet tourne vis.
à la clé numéro 8 se trouve l’objet rateau.
à la clé numéro 9 se trouve l’objet stylo.
à la clé numéro 10 se trouve l’objet poupée.
Si la table contient, à la fois, des clés numériques et des clés sous forme de chaîne de caractère, la fonction ipairs parcourra les clés numériques en ignorant les clés sous forme de chaîne de caractères.
pairs
[modifier | modifier le wikicode]La fonction pairs réalise à peu près la même chose que la fonction ipairs mais en considérant, cette fois, les tables dont les clés sont des chaînes de caractères. Nous prendrons donc, là aussi, un exemple pour mieux comprendre. Exemple, d'ailleurs très similaire à l'exemple choisi dans le paragraphe précédent :
local fouillis = {["Nourriture"] = "Fromage", ["Boisson"] = "Limonade", ["Bestiole"] = "Cafard", ["Couvert"] = "Fourchette", ["Truc"] = "Machin chose"}
function p.farfouille()
local reponse = " "
for index, objet in pairs(fouillis) do
reponse = reponse.."<br />à la clé "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
{{#invoke:Basique|farfouille}} nous retourne :
à la clé Nourriture se trouve l’objet Fromage.
à la clé Boisson se trouve l’objet Limonade.
à la clé Couvert se trouve l’objet Fourchette.
à la clé Truc se trouve l’objet Machin chose.
à la clé Bestiole se trouve l’objet Cafard.
Nous voyons que, cette fois, la table n’est pas lue dans l’ordre où on l'a écrit, ni dans l’ordre alphabétique.
Si la table contient, à la fois, des clés numériques et des clés sous forme de chaîne de caractère, la fonction pairs parcourra la totalité des clés, qu’elles soient sous forme numérique ou sous forme de chaîne de caractères.
unpack
[modifier | modifier le wikicode]Nous avons vu dans les chapitres précédents que l’on est souvent amené à présenter des données séparées par des virgules (paramètres d'une fonction, valeurs retournées par une fonction, remplissage d'une table indexée numériquement, etc.)
Si les données, que l’on veut séparer par des virgules sont rangées dans une table indexée numériquement, on peut les sortir et les présenter séparées par des virgules grâce à la fonction unpack.
unpack(table,i,j) sort les données d'une table indexée numériquement, de l'index i à l'index j et les présente séparées par des virgules.
Par exemple dans :
local nombres_premiers = {2,3,5,7,11,13,17,19,23,29,31,37,41}
function p.Calcul()
local premier = {unpack(nombres_premiers,5,8)}
etc.
end
La fonction p.calcul n'avait besoin que des nombres premiers entre 10 et 20. Elle a donc créer une table premier qui ne contient que les nombres premiers entre 10 et 20 extrait, grâce à la fonction unpack, de la table nombres_premiers. Les nombres premiers extraits ce trouvaient, dans la table nombres_premiers, de l'index 5 à l'index 8.
Nous ne sommes pas obligé de préciser les valeurs de i et j à l'appel de la fonction unpack. Par défaut i vaut 1 et j est égal au nombre d'éléments se trouvant dans la table.
select
[modifier | modifier le wikicode]La fonction select considère son premier argument et agit sur le reste de ses arguments en fonction du premier.
- Si le premier argument est 1, la fonction select retourne le reste de ses arguments. Par exemple select(1,a,b,c,d) retournera a,b,c,d
- Si le premier argument est 2, la fonction select retourne le reste de ses arguments sauf celui qui suit l'argument égal à 2. Par exemple select(2,a,b,c,d) retournera b,c,d
- Et ainsi de suite. Par exemple select(3,a,b,c,d,e,f) retournera c,d,e,f. select(4,a,b,c,d,e) retournera d,e
- Si le premier argument est #, la fonction select retournera le nombre de ses arguments sans compter le premier qui est #. Par exemple select(#,a,b,c,d,e,f,g,h) retournera 8
rawget
[modifier | modifier le wikicode]Cette fonction à deux paramètres est telle que rawget(tab,k) est équivalent à tab[k] (tab étant une table et k une clé). La seule différence est que la fonction rawget ignore une éventuelle méta-méthode associée au champ __index. Pour plus de précisions voir le chapitre sur les méta-tables
rawequal
[modifier | modifier le wikicode]Cette fonction à deux paramètres est telle que rawequal(a,b) est équivalent à a == b (a et b étant des tables). La seule différence est que la fonction rawequal ignore une éventuelle méta-méthode associée au champ __eq. Pour plus de précisions voir le chapitre sur les méta-tables
rawset
[modifier | modifier le wikicode]Cette fonction à trois paramètres est telle que rawset(tab,k,v) est équivalent à tab[k] = v (tab étant une table, k une clé et v une valeur). La seule différence est que la fonction rawset ignore une éventuelle méta-méthode associée au champ __newindex. Pour plus de précisions voir le chapitre sur les méta-tables
Chaînes de caractères
Nous allons, dans ce chapitre, étudier quelques fonctions préprogrammées permettant de faire quelques opérations sur les chaînes de caractères. Comme dans le chapitre précédent, nous ne rangerons pas nécessairement les fonctions dans l’ordre alphabétique.
Tous les exemples de ce chapitre seront rangés dans le Module:Chaine.
Généralités
[modifier | modifier le wikicode]Une chaîne de caractères se représente par un texte entouré de guillemet " ". L'affectation à une variable se présente ainsi :
phrase = "Je pense donc je suis"
Il est possible de connaître le nombre de caractères utilisés dans la chaîne de caractères en utilisant l'opérateur #
longueur = #"Je pense donc je suis"
longueur sera alors de type number et contiendra le nombre 21.
On peut aussi utiliser cet opérateur sur une variable de type string pour connaître la longueur de la chaîne de caractères qu'elle contient :
longueur = #phrase
Le caractère # ne marche pas sur l'unicode. Pour l'unicode, on utilisera la fonction mw.ustring.len |
Sans rien y mettre, on peut initialiser une variable comme étant de type string en écrivant :
local mot = ""
On rappelle l’utilisation de l'opérateur .. permettant de concaténer deux chaînes de caractères.
phrase = "Je pense donc je suis"..", mais que suis-je ?"
La variable phrase contiendra alors la chaîne de caractères : "Je pense donc je suis, mais que suis-je ?"
La librairie String
[modifier | modifier le wikicode]La librairie String est constituée d'un ensemble de fonctions rangées dans une table nommée string. Nous commencerons donc par visualiser le contenu de cette table grâce au programme suivant :
local p = {}
function p.visuString(frame)
reponse = ""
for index, objet in pairs(string) do
reponse = reponse.."<br />À la clé '''"..index.."''', on trouve un objet de type : "..type(objet)
end
return reponse
end
return p
{{#invoke:Chaine|visuString}} nous donne :
À la clé sub, on trouve un objet de type : function
À la clé find, on trouve un objet de type : function
À la clé gsub, on trouve un objet de type : function
À la clé gmatch, on trouve un objet de type : function
À la clé gfind, on trouve un objet de type : function
À la clé byte, on trouve un objet de type : function
À la clé rep, on trouve un objet de type : function
À la clé match, on trouve un objet de type : function
À la clé uupper, on trouve un objet de type : function
À la clé ulower, on trouve un objet de type : function
À la clé reverse, on trouve un objet de type : function
À la clé upper, on trouve un objet de type : function
À la clé len, on trouve un objet de type : function
À la clé format, on trouve un objet de type : function
À la clé char, on trouve un objet de type : function
À la clé lower, on trouve un objet de type : function
Nous étudions ci-dessous chacune des fonctions en détail :
string.sub
[modifier | modifier le wikicode]Cette fonction permet d'extraire une sous-chaîne d'une chaîne donnée. Elle admet trois paramètre. Le premier est la chaîne dont on veut extraire une sous-chaîne. Le deuxième est la position du premier caractère extrait. Le troisième est la position du dernier caractère extrait. Par exemple : string.sub("abcdefghijkl",3,7) devrait nous retourner "cdefg". Il est possible de donner les positions avec des nombres négatifs, cela signifie que l’on compte la position à partir de la fin de la chaîne. Si le troisième paramètre est absent, cela signifie que l’on extrait jusqu'au dernier caractère de la chaîne.
Pour tester cette fonction, nous utiliserons une fonction p.extrait ainsi écrite :
function p.extrait(frame)
phrase = frame.args[1]
debut = tonumber(frame.args[2])
fin = tonumber(frame.args[3])
return string.sub(phrase,debut,fin)
end
{{#invoke:Chaine|extrait|abcdefghijkl|3|7}} nous donne : cdefg
{{#invoke:Chaine|extrait|abcdefghijkl|-5|-2}} nous donne : hijk
En Unicode, on utilisera la fonction : mw.ustring.sub.
string.find
[modifier | modifier le wikicode]Cette fonction permet de trouver un motif particulier dans une chaîne de caractères et de retourner sa position.
Elle s'utilise sous la forme string.find(chaîne, motif, position de départ de la recherche,true)
Si la fonction ne trouve pas le motif, elle retourne nil
La position de départ est optionnelle. Si on ne la met pas, la recherche démarrera au début (dans ce cas true ne devra pas être mis non plus).
Si l’on ne met pas true en quatrième paramètre, certains caractères de la chaîne motif vont être interprétés de façon particulière. Ces caractères sont "(", ")", "&", "%". On devra donc mettre true si le motif que l’on recherche contient ces caractères sans qu’ils aient un sens particulier. Si, par exemple, on veut chercher "groupe (mathématiques)" dans un texte qui contient cette expression, on devra écrire : string.find(texte, "groupe (mathématiques)", 1,true). Si l’on se contente d'écrire : string.find(texte, "groupe (mathématiques)"), on obtiendra nil.
Écrivons une fonction p.cherche qui cherche un mot dans la phrase "Rien ne sert de courir, il faut partir à point" :
function p.cherche(frame)
local mot = frame.args[1]
local phrase = "Rien ne sert de courir,il faut partir à point"
local position = string.find(phrase,mot)
if position then
return "Le mot recherché se trouve à la position : "..position
else
return "Je n'ai pas trouvé !"
end
end
{{#invoke:Chaine|cherche|courir}} nous donne : Le mot recherché se trouve à la position : 17
Le fait de pouvoir démarrer la recherche à une position particulière peut être utile si on cherche un mot dans un paragraphe d'une page. On commence alors par chercher la position du titre du paragraphe et on cherche ensuite le mot à partir de cette position.
En Unicode, on utilisera la fonction : mw.ustring.find.
string.gsub
[modifier | modifier le wikicode]Cette fonction permet de remplacer toutes les occurrences d'un motif donné par un autre motif. Par exemple, cette fonction permet de remplacer un mot d'une phrase par un autre mot. string.gsub("Je casse un œuf dur", "casse", "mange") nous retourne : Je mange un œuf dur,1.
Nous voyons aussi que, en plus de la phrase modifiée, cette fonction nous retourne le nombre de modifications faites.
Écrivons une fonction p.oeuf pour vérifier ce que l’on vient de dire :
function p.oeuf()
local phrase,nombre = string.gsub("Je casse un œuf dur", "casse", "mange")
return phrase.."(Le nombre de mots remplacés est "..nombre..")"
end
{{#invoke:Chaine|oeuf}} nous donne : Je mange un œuf dur(Le nombre de mots remplacés est 1)
En Unicode, on utilisera la fonction : mw.ustring.gsub.
string.gmatch
[modifier | modifier le wikicode]En Unicode, on utilisera la fonction : mw.ustring.gmatch
string.gfind
[modifier | modifier le wikicode]
string.byte
[modifier | modifier le wikicode]En Unicode, on utilisera la fonction : mw.ustring.byte ou la fonction : mw.ustring.codepoint
string.rep
[modifier | modifier le wikicode]Cette fonction répète une chaîne de caractères un certain nombre de fois. Elle a donc deux arguments. Le premier argument est la chaîne de caractères à répéter et le second argument est le nombre de fois que l’on doit répéter cette chaîne. Par exemple, string.rep("cou",2) devrait nous donner "coucou". À titre d'exemple, nous écrirons une fonction p.echo qui répétera une chaîne de caractères un nombre de fois que l’on précisera :
function p.echo(frame)
local mot = frame.args[1]
local nombre = tonumber(frame.args[2])
return string.rep(mot,nombre)
end
{{#invoke:Chaine|echo|glou |3}} nous donne : glou glou glou
En Unicode, on utilisera la fonction : mw.ustring.rep
string.match
[modifier | modifier le wikicode]En Unicode, on utilisera la fonction : mw.ustring.match
string.uupper
[modifier | modifier le wikicode]
string.ulower
[modifier | modifier le wikicode]
string.reverse
[modifier | modifier le wikicode]Cette fonction renverse la chaîne de caractères qui lui est soumise. La dernière lettre devient la première, l'avant dernière devient la seconde et ainsi de suite. Par exemple : "coquillage" devient "egalliuqoc". Pour tester cette fonction, la première idée qui vient à l'esprit est d'écrire une fonction p.palindrome qui teste si un mot entré en argument est un palindrome :
function p.palindrome(frame)
local mot = frame.args[1]
if mot == string.reverse(mot) then
return mot.." est un palindrome."
else
return mot.." n’est pas un palindrome."
end
end
{{#invoke:Chaine|palindrome|radar}} nous donne : radar est un palindrome.
{{#invoke:Chaine|palindrome|renier}} nous donne : renier n’est pas un palindrome.
string.upper
[modifier | modifier le wikicode]Cette fonction permet de remplacer les caractères minuscules de la chaîne de caractères par les caractères majuscules correspondants. Par exemple, "crocodile" deviendra "CROCODILE". Essayons de voir si ça marche ! Dans le Module:Chaine, écrivons une fonction p.capitale qui utilise directement la fonction string.upper sur une chaîne de caractères rentrée en argument :
function p.capitale(frame)
local mot = frame.args[1]
return string.upper(mot)
end
{{#invoke:Chaine|capitale|crocodile}} nous donne : CROCODILE
{{#invoke:Chaine|capitale|pIScinE eT 3 pApiLloNs}} nous donne : PISCINE ET 3 PAPILLONS
Nous constatons sur le dernier exemple que les majuscules restent majuscules. Les autres caractères comme les chiffres ne sont pas modifiés.
En Unicode, on utilisera la fonction : mw.ustring.upper
string.len
[modifier | modifier le wikicode]Cette fonction nous donne le nombre de caractères contenus dans une chaîne de caractères. Les lettres accentuées comptent double. Par exemple, string.len(Marionnette) devrait nous retourner 11 car il y a 11 lettres dans le mot "Marionnette". Par contre string.len(système) nous retournera 8 à cause de la lettre accentuée è qui compte double. Comme exemple, nous écrirons une fonction p.longueur qui nous renverra le nombre de caractères d'une chaîne de caractères rentrée en argument :
function p.longueur(frame)
local phrase = frame.args[1]
return string.len(phrase)
end
{{#invoke:Chaine|longueur|Marionnette}} nous donne : 11
{{#invoke:Chaine|longueur|La vague déferle sur la plage}} nous donne : 30
Dans le deuxième exemple, nous voyons qu’il y a 29 caractères dans la phrase et pourtant la fonction string.len en compte 30 à cause de la présence de la lettre accentuée é dans la mot déferle
En Unicode, on utilisera la fonction : mw.ustring.len
Pour la fonction mw.ustring.len, les lettres accentuées ne comptent pas double. Cette fonction sera donc préférable pour traiter les chaînes de caractères pouvant contenir des lettres accentuées.
string.format
[modifier | modifier le wikicode]La fonction string.format permet d'obtenir un format particulier pour un nombre ou une chaîne de caractères.
Elle s'utilise sous la forme : string.format(option,nombre ou chaîne)
L'option permet de définir le type de format que l’on désire obtenir. Il sera écrit sous la forme : '%lettre'
Nous allons passer en revue, les différentes options possible :
L'option '%c'
Permet d'obtenir le caractère correspondant à un code ASCII particulier. Par exemple :
string.format('%c',87) donnera la lettre majuscule W.
string.format('%c',36) donnera le symbole $.
L'option '%d'
Donne la partie entière d'un nombre
string.format('%d',56.235) donnera 56.
string.format('%d',-23.827) donnera -23.
L'option '%E'
Permet d'obtenir l'écriture scientifique correspondante à un nombre donnée, la puissance de 10 étant écrite avec E.
string.format('%E',0.097) donnera 9.700000E-2.
L'option '%e'
Permet d'obtenir l'écriture scientifique correspondante à un nombre donnée, la puissance de 10 étant écrite avec e.
string.format('%e',0.097) donnera 9.700000e-2.
L'option '%f'
Permet d'obtenir un nombre décimal écrit avec six chiffres après la virgule.
string.format('%f',0.097) donnera 0.097000
string.format('%f',823.43657835) donnera 823.436578
L'option '%g'
Transforme une notation scientifique en notation décimale tout en arrondissant le nombre à six chiffres significatifs.
string.format('%g',1.5789235E4) donnera 15789.2
string.format('%g',-1.5789235e-4) donnera -0.000157892
L'option '%G'
Transforme une notation scientifique en notation décimale tout en arrondissant le nombre à six chiffres significatifs.
string.format('%G',1.5789235E4) donnera 15789.2
string.format('%G',-1.5789235e-4) donnera -0.000157892
L'option '%i'
Donne la partie entière d'un nombre
string.format('%i',56.235) donnera 56.
string.format('%i',-23.827) donnera -23.
L'option '%o'
Cette option permet de traduire un nombre écrit en base 10 en nombre écrit en base 8.
string.format('%o',9) donnera 11
string.format('%o',93) donnera 135
L'option '%u'
Convertit dans un format qui n'accepte que les nombres entiers positifs compris entre 0 et 18446744073709551615. Le nombre obtenu n'est plus à virgule flottante. Pour les nombres positifs, on obtient la partie entière. Pour les nombres négatifs on obtient la somme du nombre négatif avec 18446744073709551616.
string.format('%u',-1) donnera 18446744073709551615
string.format('%u',21.2479) donnera 21
L'option '%X'
Cette option permet de traduire un nombre en hexadécimal. Autrement dit, cette option permet de traduire un nombre écrit en base 10 en nombre écrit en base 16. Les lettres utilisées pour écrire ce nombre seront en majuscule.
string.format('%X',47) donnera 2F
string.format('%X',28) donnera 1C
L'option '%x'
Cette option permet de traduire un nombre en hexadécimal. Autrement dit, cette option permet de traduire un nombre écrit en base 10 en nombre écrit en base 16. Les lettres utilisées pour écrire ce nombre seront en minuscule.
string.format('%x',169) donnera a9
string.format('%x',735) donnera 2df
L'option '%q'
Traite une chaîne de caractère. Les guillemets sont conservés.
string.format('%q',"La voiture roule") donnera : "La voiture roule"
L'option '%s'
Traite une chaîne de caractère. Les guillemets ne sont pas conservés.
string.format('%s',"La voiture roule") donnera : La voiture roule
Nous pouvons retrouver tous les exemples précédents simulés avec la fonction p.formatage dans le Module:Chaine :
function p.formatage()
local reponse = " "
reponse = reponse.."<br>Avec c, on obtent : "..string.format('%c',87).." et "..string.format('%c',36)
reponse = reponse.."<br>Avec d, on obtent : "..string.format('%d',56.235).." et "..string.format('%d',-23.827)
reponse = reponse.."<br>Avec E, on obtent : "..string.format('%E',0.097).." et "..string.format('%E',-2833.24)
reponse = reponse.."<br>Avec e, on obtent : "..string.format('%e',0.097).." et "..string.format('%e',-2833.24)
reponse = reponse.."<br>Avec f, on obtent : "..string.format('%f',0.097).." et "..string.format('%f',823.43657835)
reponse = reponse.."<br>Avec g, on obtent : "..string.format('%g',1.5789235E4).." et "..string.format('%g',-1.5789235e-4)
reponse = reponse.."<br>Avec G, on obtent : "..string.format('%G',1.5789235E4).." et "..string.format('%G',-1.5789235e-4)
reponse = reponse.."<br>Avec i, on obtent : "..string.format('%i',56.235).." et "..string.format('%i',-23.827)
reponse = reponse.."<br>Avec o, on obtent : "..string.format('%o',9).." et "..string.format('%o',93)
reponse = reponse.."<br>Avec u, on obtent : "..string.format('%u',-1).." et "..string.format('%u',21.2479)
reponse = reponse.."<br>Avec X, on obtent : "..string.format('%X',47).." et "..string.format('%X',28)
reponse = reponse.."<br>Avec x, on obtent : "..string.format('%x',169).." et "..string.format('%x',735)
reponse = reponse.."<br>Avec q, on obtent : "..string.format('%q',"La voiture roule")
reponse = reponse.."<br>Avec s, on obtent : "..string.format('%s',"La voiture roule")
return reponse
end
{{#invoke:Chaine|formatage}} nous donne :
Avec c, on obtent : W et $
Avec d, on obtent : 56 et -23
Avec E, on obtent : 9.700000E-02 et -2.833240E+03
Avec e, on obtent : 9.700000e-02 et -2.833240e+03
Avec f, on obtent : 0.097000 et 823.436578
Avec g, on obtent : 15789.2 et -0.000157892
Avec G, on obtent : 15789.2 et -0.000157892
Avec i, on obtent : 56 et -23
Avec o, on obtent : 11 et 135
Avec u, on obtent : 18446744073709551615 et 21
Avec X, on obtent : 2F et 1C
Avec x, on obtent : a9 et 2df
Avec q, on obtent : "La voiture roule"
Avec s, on obtent : La voiture roule
En Unicode, on utilisera la fonction : mw.ustring.format
string.char
[modifier | modifier le wikicode]
string.lower
[modifier | modifier le wikicode]Cette fonction fait exactement l'inverse de la fonction string.upper, elle transforme les majuscules en minuscules dans une chaîne de caractères. Pour la tester, nous écrirons une fonction p.minuscule ainsi :
function p.minuscule(frame)
local mot = frame.args[1]
return string.lower(mot)
end
{{#invoke:Chaine|minuscule|CORNICHON}} nous donne : cornichon
{{#invoke:Chaine|minuscule|pIScinE eT 3 pApiLloNs}} nous donne : piscine et 3 papillons
Nous constatons sur le dernier exemple que les minuscules reste minuscules. Les autres caractères comme les chiffres ne sont pas modifiés.
En Unicode, on utilisera la fonction : mw.ustring.lower
Motifs (pattern)
[modifier | modifier le wikicode]En attendant une rédaction plus détaillée et moins obscure pour les non-programmeurs, cette section est issue d'une recopie d'une partie de la page : mw:Extension:Scribunto/Lua reference manual/fr
En Lua, les motifs sont similaires aux expressions régulières sans pour autant être identiques. Voici quelques différences avec les expressions régulières et les PCRE :
- Le caractère d'échappement est le symbole du pour cent
%
, pas l'antislash\
; - Le point
.
remplace tout caractère, y compris le retour chariot ; - Il n'y a pas d'option pour être insensible à la casse ;
- L'alternative
|
n’est pas définie ; - Les quantificateurs (
*
,?
,+
, et-
) ne peuvent s'appliquer qu’à un caractère ou une classe de caractère, pas à un groupe de capture ; - Le seul quantificateur non possessif est
-
, qui est équivalent au quantificateur*?
de PCRE ; - Pas moyen de quantifier de manière précise (comme le ferait
{n,m}
avec PCRE ; - Les seuls caractères de contrôle qui n'ont aucune profondeur sont
^
et$
(lua n'offre pas la possibilité d’utiliser\b
ou(?=···)
qui apparaisse dans les PCRE).
Voir aussi motifs ustring pour des motifs similaires utilisant cette fois les caractères Unicode.
Classes de caractères :
[modifier | modifier le wikicode]Une classe de caractères est utilisée pour représenter un ensemble de caractères. Les combinaisons suivantes sont autorisées pour représenter une classe de caractères :
- x : (où x n’est pas un caractère « magique »
^$()%.[]*+-?
) représente le caractère x lui-même. .
: (un point) représente tous les caractères.%a
: représente toutes les lettres (A-Za-z, mais pas é, è, û...).%c
: représente tous les caractères de contrôle.%d
: représente tous les chiffres.%l
: représente toutes les lettres minuscules (a-z, mais pas é, è, û...).%p
: représente tous les signes de ponctuation.%s
: représente tous les caractères séparateurs (espace, saut de ligne, …).%u
: représente toutes les lettres majuscules (A-Z, mais pas É, Ê...).%w
: représente tous les caractères alphanumériques (A-Za-z0-9, mais pas é, è, û...).%x
: représente tous les chiffres hexadécimaux.%z
: représente le caractère\0
(le caractère dont la valeur numérique est nulle).%x
: (où x est n’importe quel caractère non alphanumérique) représente le caractère x. Ceci est la façon habituelle d’utiliser explicitement un caractère « magique » (ayant un sens de contrôle). Tout caractère de ponctuation (même les non magiques) peut être précédé d'un%
quand il représente lui-même.[set]
: représente la classe qui est l'union de tous les caractères présents dans set. On peut indiquer un intervalle de caractère en séparant les caractères qui bornent cet intervalle avec un-
.
Toutes les classes %
x décrites plus haut peuvent aussi être utilisées dans un set.
Tous les autres caractères dans un set représentent eux-mêmes. Par exemple [%w_]
(ou [_%w]
) représente tous les caractères alphanumériques et le tiret bas (underscore), [0-7]
représente les chiffres octaux, et [0-7%l%-]
représente les chiffres octaux plus les lettre minuscules plus le caractère -
. Utiliser à la fois les intervalles et les classes a un effet non spécifié. Ainsi, des motifs comme [%a-z]
ou [a-%%]
n'ont pas de sens.
[^set]
: représente le complément de set (voir l'interprétation de set ci-dessus), c'est-à-dire tous les caractères n'appartenant pas à set.
Pour toutes les classes représentées par une seule lettre (%a
, %c
, …), la lettre majuscule correspondante représente le complément de cette classe.
Par exemple %S
représente tous les caractères qui ne sont pas des séparateurs.
Élément de motif (pattern item) :
[modifier | modifier le wikicode]Un élément de motif peut être :
- un singleton d'une classe de caractère, qui correspond à un caractère unique dans la classe ;
- un singleton d'une classe de caractère suivi d'un
*
, qui correspond à 0 ou plus répétition(s) de tout caractère de la classe. Cette répétition correspond toujours à la plus longue répétition possible ; - un singleton d'une classe de caractère suivi d'un
+
, qui correspond à 1 ou plus répétition(s) de tout caractère de la classe. Cette répétition correspond toujours à la plus longue répétition possible ; - un singleton d'une classe de caractère suivi d'un
-
, qui correspond également à 0 ou plus répétition(s) de tout caractère de la classe. Contrairement à*
, cette répétition correspond toujours à la plus petite répétition possible ; - un singleton d'une classe de caractère suivi d'un
?
, qui correspond à 0 ou 1 occurrence d'un caractère de la classe ; %n
, pour n compris entre 1 et 9. Cet élément correspond à une sous-chaîne valant la n-ième chaîne capturée (voir plus bas) ;%bxy
, ou x et y sont deux caractères distincts. Cet élément correspond à la chaîne qui commence par x, se termine par y, et où x et y sont équilibrés (balanced). Cela signifie, en lisant la chaîne de gauche à droite, compter +1 pour chaque x et -1 pour chaque y, le y terminal est le premier y pour lequel le compte atteint 0. Par exemple, l'élément%b()
correspond à une expression avec des parenthèses équilibrées.
Motif (pattern) :
[modifier | modifier le wikicode]Un motif est une séquence d'éléments de motif.
Un ^
au début d'un motif correspond au début de la ligne de la chaîne traitée.
Un $
à la fin du motif correspond à la fin de la ligne de la chaîne traitée.
Aux autres positions, ^
et $
n'ont pas de sens particulier et représentent le caractère qui les constitue.
Captures :
[modifier | modifier le wikicode]Un motif (pattern) peut contenir des sous-motifs entourés de parenthèses, qui décrivent des captures.
Quand une correspondance réussit, la sous-chaîne de la chaîne qui correspond est stockée (capturée) pour usage ultérieur.
Les captures sont numérotées dans l’ordre de leur parenthèse de gauche.
Par exemple, dans le motif "(a*(.)%w(%s*))"
, la partie de la chaîne qui concorde avec "a*(.)%w(%s*)"
est stockée dans la première capture (et a donc le numéro 1) ; le caractère qui concorde avec ".
" est stocké dans la capture numéro 2, et la partie qui concorde avec "%s*
" a le numéro 3.
La capture vide ()
est spéciale et capture la position courante dans la chaîne (un nombre).
Par exemple si on applique le motif "()aa()"
sur la chaîne "flaaap"
, il y aura deux captures : 3 et 5.
Un motif ne peut contenir de zéros ("\0"
). Utilisez %z
à la place.
La librairie Ustring
[modifier | modifier le wikicode]En attendant une rédaction plus détaillée et moins obscure pour les non-programmeurs, des fonctions de cette librairie, cette section est issue d'une recopie d'une partie de la page : mw:Extension:Scribunto/Lua reference manual/fr
La librairie Ustring est une reprise de la librairie string étudiée précédemment, mais pouvant opérer sur des caractères UTF-8.
La plupart des fonctions génèrent une erreur si la chaîne n’est pas valide en codage UTF-8.
mw.ustring.maxPatternLength
[modifier | modifier le wikicode]Donne la taille maximum autorisée pour un motif (pattern), en octets.
mw.ustring.maxStringLength
[modifier | modifier le wikicode]Donne la taille maximum autorisée pour une chaîne, en octets.
mw.ustring.byte
[modifier | modifier le wikicode]mw.ustring.byte( s, i, j )
Retourne les octets d'une chaîne. Identique à string.byte().
mw.ustring.byteoffset
[modifier | modifier le wikicode]mw.ustring.byteoffset( s, l, i )
Retourne la position en octets d'un caractère dans la chaîne. La valeur par défaut pour l
et i
est 1. i
peut être négatif, auquel cas c’est une position à partir de la fin.
Le caractère à l
== 1 est le premier caractère qui commence à ou à partir de la position i
. Le caractère à l
== 0 est le premier caractère qui commence à ou avant la position i
. Notez que ce peut être le même caractère. Des valeurs plus grandes ou plus petites de l
sont calculées en relatif. (NDT : pas clair. À préciser)
mw.ustring.char
[modifier | modifier le wikicode]mw.ustring.char( ... )
Similaire à string.char(), mais les entiers retournés sont des points de codes Unicode et non des octets.
mw.ustring.codepoint
[modifier | modifier le wikicode]mw.ustring.codepoint( s, i, j )
Similaire à string.byte(), mais les valeurs retournées sont des points de code Unicode et les positions sont celles des caractères et non des octets.
mw.ustring.find
[modifier | modifier le wikicode]mw.ustring.find( s, pattern, init, plain )
Similaire à string.find(), mais le motif (pattern) est géré comme décrit dans motifs ustring et la position init
est en caractères et non en octets.
- Exemple Unicode
print mw.ustring.find("Salut à vous", "à") -- 7 7
Si la chaine recherchée contient des opérateurs regex, ils peuvent être interprétés. Pour s'en prémunir, il peut être utile de les échapper à l’aide de la fonction suivante :
function p._escapePattern( pattern_str )
return mw.ustring.gsub( pattern_str, "([%(%)%.%%%+%-%*%?%[%^%$%]])", "%%%1" );
end
|
- Exemple avec "?"
print mw.ustring.find("Comment allez-vous ?", "vous ?") -- "15 19"
print mw.ustring.find("Comment allez-vous ?", p._escapePattern("vous ?")) -- "15 20"
mw.ustring.format
[modifier | modifier le wikicode]mw.ustring.format( format, ... )
Identique à string.format(). Les tailles et précisions pour les chaînes sont en octets et non en points de code Unicode.
mw.ustring.gcodepoint
[modifier | modifier le wikicode]mw.ustring.gcodepoint( s, i, j )
Retourne trois valeurs pour itérer sur les points de code Unicode de la chaîne. i
vaut 1 par défaut et j
vaut -1 par défaut. Ca sert à être utilisé par la forme itérateur de for
:
for codepoint in mw.ustring.gcodepoint( s ) do block end
mw.ustring.gmatch
[modifier | modifier le wikicode]mw.ustring.gmatch( s, pattern )
Similaire à string.gmatch(), mais le motif (pattern) est géré comme décrit dans motifs ustring.
mw.ustring.gsub
[modifier | modifier le wikicode]mw.ustring.gsub( s, pattern, repl[, n] )
Similaire à string.gsub(), mais le motif (pattern) est géré comme décrit dans motifs ustring.
mw.ustring.isutf8
[modifier | modifier le wikicode]mw.ustring.isutf8( s )
Retourne vrai si la chaîne est en UTF-8 valide, faux sinon.
mw.ustring.len
[modifier | modifier le wikicode]mw.ustring.len( s )
Retourne la longueur de la chaîne en point de code Unicode ou nil si la chaîne n’est pas en UTF-8 valide.
Voir string.len() pour une fonction similaire qui utilise la longueur des bits plutôt que des points de code.
mw.ustring.lower
[modifier | modifier le wikicode]mw.ustring.lower( s )
Similaire à string.lower() à part que la fonction suit le format Unicode.
Si la librairie Language est également chargée, ceci utilisera lc() sur la langue courante à la place.
mw.ustring.match
[modifier | modifier le wikicode]mw.ustring.match( s, pattern, init )
Similaire à string.match(), mais le motif (pattern) est géré comme décrit dans motifs ustring et la position init
est en caractères et non en octets.
mw.ustring.rep
[modifier | modifier le wikicode]mw.ustring.rep( s, n )
Identique à string.rep().
mw.ustring.sub
[modifier | modifier le wikicode]mw.ustring.sub( s, i, j )
Similaire à string.sub(), mais les positions sont en caractères et non en octets.
mw.ustring.toNFC
[modifier | modifier le wikicode]mw.ustring.toNFC( s )
Convertit la chaîne en forme normalisée C. Retourne nil si la chaîne n’est pas valide en UTF-8.
mw.ustring.toNFD
[modifier | modifier le wikicode]mw.ustring.toNFD( s )
Convertit la chaîne en forme normalisée D. Retourne nil si la chaîne n’est pas valide en UTF-8.
mw.ustring.upper
[modifier | modifier le wikicode]mw.ustring.upper( s )
Similaire à string.upper() à part que la fonction suit le format Unicode.
Si la librairie Language est également chargée, ceci utilisera uc() sur la langue courante à la place.
Motifs ustring
[modifier | modifier le wikicode]Appelé aussi pattern, les motifs dans les fonctions ustring
utilisent la même syntaxe que celle des motifs de la librairie String. La différence principale est que les classes de caractères sont redéfinis en termes de propriétés de caractères Unicode :
%a
: représente tous les caractères de la catégorie "Lettre"%c
: représente tous les caractères de la catégorie "Contrôle"%d
: représente tous les caractères de la catégorie "Nombre décimal".%l
: représente tous les caractères de la catégorie "Lettre minuscule".%p
: représente tous les caractères de la catégorie "Ponctuation".%s
: représente tous les caractères de la catégorie "Séparateur", plus tabulation, saut de ligne, retour chariot, tabulation verticale, et saut de page.%u
: représente tous les caractères de la catégorie "Lettre majuscule"%w
: représente tous les caractères de la catégorie "Lettre" ou "Nombre décimal"%x
: ajoute la version complète des caractères hexadécimaux
Dans tous les cas, les caractères sont interprétés comme des caractères Unicode et non des octets, donc des séries comme [0-9]
, des motifs comme %b«»
, et des quantificateurs appliqués à des caractères multi-octets fonctionnent correctement. La capture vide capturera des positions en codes Unicode et non en octets.
La librairie Text
[modifier | modifier le wikicode]Les exemples de ce chapitre se trouveront dans le Module:Text
Nous commencerons par visualiser le contenu de la librairie Text grâce au programme suivant :
local p = {}
function p.visualisation(frame)
reponse = ""
for index, objet in pairs(mw.text) do
reponse = reponse.."<br />À la clé "..index..", on trouve un objet de type : "..type(objet)
end
return reponse
end
return p
{{#invoke:Text|visualisation}} nous donne :
À la clé listToText, on trouve un objet de type : function
À la clé gsplit, on trouve un objet de type : function
À la clé nowiki, on trouve un objet de type : function
À la clé encode, on trouve un objet de type : function
À la clé JSON_PRETTY, on trouve un objet de type : number
À la clé decode, on trouve un objet de type : function
À la clé jsonEncode, on trouve un objet de type : function
À la clé truncate, on trouve un objet de type : function
À la clé trim, on trouve un objet de type : function
À la clé killMarkers, on trouve un objet de type : function
À la clé tag, on trouve un objet de type : function
À la clé unstripNoWiki, on trouve un objet de type : function
À la clé split, on trouve un objet de type : function
À la clé JSON_PRESERVE_KEYS, on trouve un objet de type : number
À la clé jsonDecode, on trouve un objet de type : function
À la clé JSON_TRY_FIXING, on trouve un objet de type : number
À la clé unstrip, on trouve un objet de type : function
Nous voyons que nous avons 12 fonctions. Étudions les dans l’ordre où elles sont sorties.
mw.text.listToText
[modifier | modifier le wikicode]mw.text.listToText( list )
mw.text.listToText( list, separator, conjunction )
Joint une liste sous forme « textuelle ». Fait la même chose que table.concat()
mais avec un séparateur différent pour l'élément final.
Le séparateur par défaut est récupéré dans MediaWiki:comma-separator dans la langue locale, et la conjonction par défaut est le MediaWiki:and concaténé avec le MediaWiki:word-separator.
Exemples :
-- retourne une chaîne vide mw.text.listToText( {} ) -- retourne "1" mw.text.listToText( { 1 } ) -- retourne "1 et 2" mw.text.listToText( { 1, 2 } ) -- retourne "1, 2, 3, 4 et 5" mw.text.listToText( { 1, 2, 3, 4, 5 } ) -- retourne "1; 2; 3; 4 ou 5" mw.text.listToText( { 1, 2, 3, 4, 5 }, '; ', ' ou ' )
mw.text.gsplit
[modifier | modifier le wikicode]mw.text.gsplit( s, pattern, plain )
Retourne une fonction itérative qui va itérer sur les sous-chaînes qui auraient été retournées par un appel équivalent à mw.text.split()
.
mw.text.truncate
[modifier | modifier le wikicode]mw.text.truncate( text, length )
mw.text.truncate( text, length, ellipsis )
mw.text.truncate( text, length, ellipsis, adjustLength )
Tronque text
à la longueur indiquée, en ajoutant ellipsis
si une troncature est effectuée. Si length est positif, la fin de la chaîne est tronquée ; s'il est négatif, c’est le début de la chaîne qui est enlevé. Si adjustLength
est présent et vrai, le résultat en incluant ellipsis ne sera pas plus long que la longueur précisée.
La valeur par défaut pour ellipsis
est prise dans MediaWiki:ellipsis pour le wiki local.
Exemples utilisant "..." pour ellipsis :
-- retourne "foobarbaz" mw.text.truncate( "foobarbaz", 9 ) -- retourne "fooba..." mw.text.truncate( "foobarbaz", 5 ) -- retourne "...arbaz" mw.text.truncate( "foobarbaz", -5 ) -- retourne "foo..." mw.text.truncate( "foobarbaz", 6, nil, true ) -- retourne "foobarbaz", car c’est plus court que "foobarba..." mw.text.truncate( "foobarbaz", 8 )
mw.text.encode
[modifier | modifier le wikicode]mw.text.encode( s )
mw.text.encode( s, charset )
Remplace les caractères de la chaîne par des entités HTML. Les caractères '<', '>', '&', '"', et l'espace insécable sont remplacés par l'entité HTML correspondante. Tous les autres sont remplacés par l'entité HTML numérique correspondante.
Si charset
est indiqué, il doit contenir une chaîne pouvant être utilisée dans une classe de caractères des motifs Ustring, par exemple "set" dans [set]
. Par défaut : '<>&"\' '
(l'espace à la fin est l'espace insécable U+00A0).
mw.text.trim
[modifier | modifier le wikicode]mw.text.trim( s )
mw.text.trim( s, charset )
Enlève les espaces et autres caractères au début et à la fin d'une chaîne.
Si charset
est indiqué, il doit contenir une chaîne syntaxiquement compatible avec les classes de caractères des motifs Ustring, par exemple "set" dans [set]
. Par défaut : l'espace ASCII, "%t%r%n%f"
.
mw.text.killMarkers
[modifier | modifier le wikicode]mw.text.killMarkers( s )
Retire d'une chaîne toutes les balises spécifiques à MediaWiki.
mw.text.tag
[modifier | modifier le wikicode]mw.text.tag{ name = string, attrs = table, content = string|bool }
- Notez l’utilisation de paramètres nommés.
Génère un tag à la façon HTML pour name
.
Si attrs
est indiqué, il doit être une table avec des clés de type chaîne. Les valeurs de ces clés sont utilisées comme valeurs des attributs. Une valeur booléenne true insère un paramètre HTML5 sans valeur, et false ignore le paramètre. Toute autre valeur génère une erreur.
Si content
n’est pas présent (ou vaut nil), seul le tag ouvrant est retourné. Si content
vaut false, un tag auto-fermant est retourné. Sinon il doit être un nombre ou une chaîne auquel cas ce contenu est inséré entre l'ouverture et la fermeture du tag. Notez que le contenu n’est pas automatiquement encodé en HTML ; utiliser mw.text.encode() au besoin.
Pour retourner proprement un tag comme <ref>
, utilisez plutôt frame:extensionTag().
mw.text.decode
[modifier | modifier le wikicode]mw.text.decode( s )
mw.text.decode( s, decodeNamedEntities )
Remplace les entités HTML de la chaîne par les caractères correspondant.
Si decodeNamedEntities
est absent ou false, les seules entités reconnues sont '<', '>', '&', '"', et ' '. Sinon la liste des entitées HTML5 à traiter est chargée depuis la fonction PHP get_html_translation_table
.
mw.text.nowiki
[modifier | modifier le wikicode]mw.text.nowiki( s )
Remplace divers caractères dans la chaîne par des entités HTML pour éviter leur interprétation comme wikitexte. Ceci comprend :
- Les caractères suivants : '"', '&', "'", '<', '=', '>', '[', ']', '{', '|', '}'
- Les caractères suivants au début de la chaîne ou juste après un retour à la ligne : '#', '*', ':', ';', espace, tabulation ('\t')
- Les lignes blanches auront le caractère de nouvelle ligne (LF) ou de retour chariot (CR) d'échappé
- "----" au début de la chaine ou juste après un saut de ligne verra son premier '-' échappé
- "__" aura un tiret bas d'échappé
- "://" aura le deux-point échappé
- Un espace blanc suivant "ISBN", "RFC", ou "PMID" sera échappé
mw.text.split
[modifier | modifier le wikicode]mw.text.split( s, pattern, plain )
Découpe une chaîne en sous-chaînes dont les limites correspondent au motif Ustring pattern
. Si plain
est présent et vrai, pattern
est interprété comme une chaîne littérale et non comme un motif Lua (comme le paramètre de même nom pour mw.ustring.find()
). Retourne une table contenant les sous-chaînes.
Par exemple mw.text.split( 'a b\tc\nd', '%s' )
retourne une table { 'a', 'b', 'c', 'd' }
.
Si pattern
correspond à la chaîne vide, s
sera découpé en caractères individuels.
mw.text.unstripNoWiki
[modifier | modifier le wikicode]mw.text.unstripNoWiki( s )
Renvoie la chaine entre balises <nowiki>. Les autres balises restent inchangées.
mw.text.unstrip
[modifier | modifier le wikicode]mw.text.unstrip( s )
Équivalent à mw.text.killMarkers( mw.text.unstripNoWiki( s ) )
.
This no longer reveals the HTML behind special page transclusion, <ref> tags, and so on as it did in earlier versions of Scribunto.
Fonctions mathématiques
Nous allons, dans ce chapitre, étudier quelques fonctions préprogrammées dans le domaine des mathématiques. Comme dans le chapitre précédent, nous ne rangerons pas les fonctions dans l’ordre alphabétique, mais nous essaierons de classer les fonctions de la plus simple à la plus compliqué ou de la plus utile à la moins utile.
Tous les exemples de ce chapitre seront rangés dans le Module:Maths.
Fonctions simples
[modifier | modifier le wikicode]Nous avons pas mal de fonctions mathématiques à étudier. Pour simplifier l'étude nous allons étudier, dans ce paragraphe, en même temps, toutes les fonctions mathématiques classiques qui acceptent en entrée un nombre réel quelconque et qui retourne un seul nombre réel. Ce sont les fonctions :
- math.abs ; Retourne la valeur absolue d'un nombre réel.
- math.exp ; Retourne l'exponentielle d'un nombre réel.
- math.cos ; Retourne le cosinus d'un nombre réel.
- math.sin ; Retourne le sinus d'un nombre réel.
- math.cosh ; Retourne le cosinus hyperbolique d'un nombre réel.
- math.sinh ; Retourne le sinus hyperbolique d'un nombre réel.
- math.tanh ; Retourne la tangente hyperbolique d'un nombre réel.
- math.atan ; Retourne l'arc tangente d'un nombre réel.
- math.deg ; Traduit en degré, un nombre donné en radian.
- math.rad ; Traduit en radian, un nombre donné en degré.
- math.ceil ; Retourne le plus petit entier supérieur ou égal à un nombre réel donné.
- math.floor ; Retourne le plus grand entier inférieur ou égal à un nombre réel donné.
Pour tester toutes ces fonctions, nous allons écrire une fonction diverse qui accepte un nombre réel en argument et qui nous retourne l'image de ce nombre réel par toutes les fonctions précédentes :
local p = {}
function p.diverse(frame)
local x = tonumber(frame.args[1])
local reponse = " "
reponse = reponse.."<br>La valeur absolu est "..math.abs(x)
reponse = reponse.."<br>L'exponentielle est "..math.exp(x)
reponse = reponse.."<br>Le cosinus est "..math.cos(x)
reponse = reponse.."<br>Le sinus est "..math.sin(x)
reponse = reponse.."<br>Le cosinus hyperbolique est "..math.cosh(x)
reponse = reponse.."<br>Le sinus hyperbolique est "..math.sinh(x)
reponse = reponse.."<br>La tangente hyperbolique est "..math.tanh(x)
reponse = reponse.."<br>L'arc tangente est "..math.atan(x)
reponse = reponse.."<br>La conversion en degrés est "..math.deg(x)
reponse = reponse.."<br>La conversion en radian est "..math.rad(x)
reponse = reponse.."<br>L'entier immédiatement supérieur est "..math.ceil(x)
reponse = reponse.."<br>L'entier immédiatement inférieur est "..math.floor(x)
return reponse
end
return p
{{#invoke:Maths|diverse|5.2713}} nous indique :
La valeur absolu est 5.2713
L'exponentielle est 194.66886754927
Le cosinus est 0.53026323894546
Le sinus est -0.84783305987857
Le cosinus hyperbolique est 97.337002238748
Le sinus hyperbolique est 97.331865310524
La tangente hyperbolique est 0.99994722532947
L'arc tangente est 1.3833176461628
La conversion en degrés est 302.02324254731
La conversion en radian est 0.092001540860377
L'entier immédiatement supérieur est 6
L'entier immédiatement inférieur est 5
{{#invoke:Maths|diverse|0.13}} nous indique :
La valeur absolu est 0.13
L'exponentielle est 1.1388283833246
Le cosinus est 0.99156189371479
Le sinus est 0.12963414261969
Le cosinus hyperbolique est 1.0084619071226
Le sinus hyperbolique est 0.13036647620203
La tangente hyperbolique est 0.12927258360606
L'arc tangente est 0.12927500404814
La conversion en degrés est 7.4484513367007
La conversion en radian est 0.0022689280275926
L'entier immédiatement supérieur est 1
L'entier immédiatement inférieur est 0
{{#invoke:Maths|diverse|-0.5}} nous indique :
La valeur absolu est 0.5
L'exponentielle est 0.60653065971263
Le cosinus est 0.87758256189037
Le sinus est -0.4794255386042
Le cosinus hyperbolique est 1.1276259652064
Le sinus hyperbolique est -0.52109530549375
La tangente hyperbolique est -0.46211715726001
L'arc tangente est -0.46364760900081
La conversion en degrés est -28.647889756541
La conversion en radian est -0.0087266462599716
L'entier immédiatement supérieur est -0
L'entier immédiatement inférieur est -1
{{#invoke:Maths|diverse|-7.333}} nous indique :
La valeur absolu est 7.333
L'exponentielle est 0.00065360981349759
Le cosinus est 0.49773177909927
Le sinus est -0.8673310072139
Le cosinus hyperbolique est 764.98272100184
Le sinus hyperbolique est -764.98206739203
La tangente hyperbolique est -0.99999914558879
L'arc tangente est -1.4352625273451
La conversion en degrés est -420.14995116943
La conversion en radian est -0.12798499404874
L'entier immédiatement supérieur est -7
L'entier immédiatement inférieur est -8
Fonction simple avec domaine de définition limité
[modifier | modifier le wikicode]Dans ce paragraphe, nous allons étudier les fonctions mathématiques qui ne sont pas définie sur la totalité de l’ensemble des nombres réels et qui nous retourne un seul réel. Ces fonctions sont :
- math.log ; Retourne le logarithme népérien d'un nombre réel.
- math.log10 ; Retourne le logarithme décimal d'un nombre réel.
- math.tan ; Retourne la tangente d'un nombre réel.
- math.acos ; Retourne l'arc cosinus d'un nombre réel.
- math.asin ; Retourne l'arc sinus d'un nombre réel.
- math.sqrt ; Retourne la racine carrée d'un nombre réel.
Toutes ces fonctions ne sont pas définies sur la totalité de l’ensemble des nombres réels. Si on leur donne en argument, un nombre réel qui n'appartient pas à leur domaine de définition, ces fonctions retourneront nan ou -nan. On remarque qu'elle ne retourne pas nil comme on aurait pu le croire à priori.
Écrivons une fonction diverse2 qui, comme le paragraphe précédent permet d’avoir le retour de toutes ces fonctions à un nombre donné :
local p = {}
function p.diverse2(frame)
local x = tonumber(frame.args[1])
local reponse = " "
reponse = reponse.."<br>Le logarithme népérien est "..math.log(x)
reponse = reponse.."<br>Le logarithme décimal est "..math.log10(x)
reponse = reponse.."<br>La tangente est "..math.tan(x)
reponse = reponse.."<br>L'arc cosinus est "..math.acos(x)
reponse = reponse.."<br>L'arc sinus est "..math.asin(x)
reponse = reponse.."<br>La racine carré est "..math.sqrt(x)
return reponse
end
return p
{{#invoke:Maths|diverse2|1.570796326794896}} nous indique :
Le logarithme népérien est 0.45158270528945
Le logarithme décimal est 0.19611987703015
La tangente est 1.3748233863972e+15
L'arc cosinus est nan
L'arc sinus est nan
La racine carré est 1.2533141373155
{{#invoke:Maths|diverse2|0.13}} nous indique :
Le logarithme népérien est -2.0402208285266
Le logarithme décimal est -0.88605664769316
La tangente est 0.13073731800446
L'arc cosinus est 1.4404273470918
L'arc sinus est 0.13036897970315
La racine carré est 0.3605551275464
{{#invoke:Maths|diverse2|-0.5}} nous indique :
Le logarithme népérien est nan
Le logarithme décimal est nan
La tangente est -0.54630248984379
L'arc cosinus est 2.0943951023932
L'arc sinus est -0.5235987755983
La racine carré est -nan
{{#invoke:Maths|diverse2|-7.333}} nous indique :
Le logarithme népérien est nan
Le logarithme décimal est nan
La tangente est -1.7425670685193
L'arc cosinus est nan
L'arc sinus est nan
La racine carré est -nan
La valeur 1.570796326794896 est une valeur approchée de π/2 qui n'appartient pas au domaine de définition de la fonction tangente. Pour cette valeur, nous avons malgré tout obtenu 1.3748233863972e+15 et non pas nan. On peut simuler la constante π à l'aide de la fonction préprogrammée math.pi. Essayons d'écrire une fonction tanpi qui calcule la tangente de π/2 en utilisant math.pi :
local p = {}
function p.tanpi()
return math.tan(math.pi/2)
end
return p
{{#invoke:Maths|tanpi}} nous donne : 1.6331239353195e+16
Nous constatons que nous obtenons une valeur encore plus grande que précédemment, mais nous n'obtenons toujours pas nan. Bien que la fonction math.tan soit à domaine de définition limité, il semblerait qu’il soit impossible de lui donner, en argument, un nombre réel tel qu'elle nous retourne nan.
math.max
[modifier | modifier le wikicode]Cette fonction retourne le plus grand de ses paramètres.
math.min
[modifier | modifier le wikicode]Cette fonction retourne le plus petit de ses paramètres.
math.modf
[modifier | modifier le wikicode]Cette fonction mathématiques permet d'obtenir la partie entière et la partie fractionnaire d'un nombre donné. Elle retourne donc deux valeurs. À titre d'exemple, nous écrirons une fonction p.separation qui nous indiquera, sous forme rédigée, la partie entière et la partie fractionnaire d'un nombre décimal entré en argument :
local p = {}
function p.separation(frame)
local a,b = math.modf(frame.args[1])
return "La partie entière est "..a.." et la partie fractionnaire est "..b
end
return p
{{#invoke:Maths|separation|5.2713}} nous indique : La partie entière est 5 et la partie fractionnaire est 0.2713
math.fmod
[modifier | modifier le wikicode]Cette fonction admet deux paramètres, math.fmod(n,d). Elle retourne le reste de la division de n par d. Une autre façon de voir serait de dire qu'elle nous donne la classe de congruence de n modulo d.
math.random et math.randomseed
[modifier | modifier le wikicode]math.random est une fonction qui retourne un nombre pseudo-aléatoire. Si l’on ne met pas de paramètre, ce nombre pseudo aléatoire sera dans l'intervalle [0;1[. Si l’on y met un paramètre n entier, le nombre pseudo-aléatoire sera un entier dans l'intervalle [1;n]. Si l’on y met deux paramètres entiers m et n, le nombre pseudo-aléatoire sera un entier dans l'intervalle [m;n].
Si la fonction est utilisée plusieurs fois dans un programme, elle donnera une liste de nombres pseudos aléatoires qui seront toujours les mêmes à chaque exécution du programme. Si l’on veut une liste de nombres différentes, il faut utiliser la fonction math.randomseed. La fonction math.randomseed admet un paramètre et selon la valeur de ce paramètre, la fonction math.random fournira une liste de nombres aléatoires qui sera différentes.
Seule la partie entière du paramètre passée à la fonction math.randomseed sera utilisée. La fonction math.randomseed ne fera pas la différence, par exemple, entre 47.3 et 47.8. Autant se contenter de lui transmettre un nombre entier ! |
Bien sûr, si l’on fournit toujours le même paramètre à la fonction math.randomseed, on obtiendra toujours la même liste. Si on veut une liste de nombre qui semble vraiment aléatoire et différente à chaque utilisation du programme, il faut trouver un moyen de fournir à la fonction math.randomseed un paramètre différent à chaque utilisation du programme. La première méthode est que l'utilisateur du programme choisisse lui-même un paramètre différent à chaque exécution du programme. Ici, cela risque d’être difficile à réaliser compte tenu que l’on ne peut pas rentrer un paramètre en temps réel à chaque utilisation du programme.
La seconde méthode consiste à utiliser une fonction qui fasse appel à une donnée extérieure au programme et qui soit susceptible de varier d'une exécution à l'autre comme par exemple l’heure à laquelle démarre le programme. Nous approfondirons cette idée en exercice.
Pour le moment, écrivons, dans le Module:maths, une fonction p.alea qui teste ce que l’on vient de dire. C'est-à-dire qui utilise la fonction math.randomseed, auquel on aura transmit un paramètre grâce à #invoke, suivi de la fonction math.random qui fournira une vingtaine de nombres aléatoires grâce à une boucle for..do. Pour être complet, nous pourrons aussi transmettre au programme les deux paramètres m et n décrit dans la présentation de la fonction math.random.
local p = {}
function p.alea(frame)
math.randomseed(frame.args[1])
local m,n = frame.args[2],frame.args[3]
local reponse = ""
for x = 1, 20 do
if m ~= nil and n ~= nil then
reponse = reponse..math.random(m,n).." "
elseif m ~= nil then
reponse = reponse..math.random(m).." "
else
reponse = reponse..math.random().." "
end
end
return reponse
end
return p
{{#invoke:Maths|alea|3|5|15}} nous donne : 12 5 5 13 13 9 15 8 8 11 7 7 7 5 15 9 14 12 6 15
Autres fonctions standards
Dans les chapitres précédents, nous avons étudié certaines catégories de fonctions. Ces catégories étaient suffisamment grandes pour que nous leur consacrions un chapitre entier. Il nous reste toutefois à étudier d'autres catégories de fonctions qui contiennent peu de fonctions. Nous consacrons donc ici ce chapitre aux fonctions qui appartiennent aux catégories restantes.
Fonctions concernant les tables
[modifier | modifier le wikicode]Les exemples donnés dans ce paragraphe se trouvent dans le Module:Tables.
table.insert
[modifier | modifier le wikicode]La fonction table.insert permet de rajouter un élément dans une table avec clé numérique, mais pas forcément à la fin comme on a l'habitude de le faire. La fonction insert permet d'insérer un élément à une clé déjà existante en décalant tous les objets déjà présents à partir de cette clé. Prenons un exemple pour mieux comprendre.
Des voitures s’apprêtent à faire une course sur un circuit. Supposons les voitures numérotées dans lesquelles se trouvent des conducteurs. Supposons que Natacha arrive en retard et veuille absolument conduire la voiture 3 (déjà occupé) car 3 est son numéro fétiche. On va donc donner la voiture 3 à Natacha. Le conducteur de la 3 ira dans la 4. Le conducteur de la 4 ira dans la 5 et ainsi de suite. Ceci est réalisé grâce à l'instruction table.insert(circuit,3,"Natacha")
Le programme décrivant l'occupation des voitures avant et après l'arrivée de Natacha est le suivant :
local p = {}
local circuit = {"Laurent","Cécile","Alain","Cloé","Amandine"}
function p.insertion()
local reponse = " "
reponse = reponse.."<br><u>voitures avant l'arrivée de Natacha.</u>"
for voiture, conducteur in ipairs(circuit) do
reponse = reponse.."<br>La voiture "..voiture.." est conduite par "..conducteur.."."
end
table.insert(circuit,3,"Natacha")
reponse = reponse.."<br><u>voitures après l'arrivée de Natacha dans la voiture 3.</u>"
for voiture, conducteur in ipairs(circuit) do
reponse = reponse.."<br>La voiture "..voiture.." est conduite par "..conducteur.."."
end
return reponse
end
return p
En tapant {{#invoke:Tables|insertion}}, on obtient :
voitures avant l'arrivée de Natacha.
La voiture 1 est conduite par Laurent.
La voiture 2 est conduite par Cécile.
La voiture 3 est conduite par Alain.
La voiture 4 est conduite par Cloé.
La voiture 5 est conduite par Amandine.
voitures après l'arrivée de Natacha dans la voiture 3.
La voiture 1 est conduite par Laurent.
La voiture 2 est conduite par Cécile.
La voiture 3 est conduite par Natacha.
La voiture 4 est conduite par Alain.
La voiture 5 est conduite par Cloé.
La voiture 6 est conduite par Amandine.
table.remove
[modifier | modifier le wikicode]La fonction table.remove réalise le contraire de ce que faisait la fonction table.insert. Elle retire un élément de la table avec clé numérique et décale les éléments suivants de façon à boucher le trou.
Reprenons l'exemple précédent en supposant, cette fois qu'Alain, le conducteur de la voiture 3, ne se sente pas bien et renonce à prendre le départ. Pour éviter qu’il y ait une discontinuité dans la numérotation des voitures, le conducteur de la voiture 4 va aller dans la 3. Le conducteur de la 5 va aller dans la 4 et ainsi de suite. Ceci est réalisé grâce à l'instruction table.remove(circuit,3). Cette fonction retourne l'élément retiré.
Le programme décrivant l'occupation des voitures avant et après le départ d'Alain est le suivant :
local p = {}
local circuit = {"Laurent","Cécile","Alain","Cloé","Amandine"}
function p.retrait()
local reponse = " "
local souffrant
reponse = reponse.."<br><u>voitures au départ du circuit.</u>"
for voiture, conducteur in ipairs(circuit) do
reponse = reponse.."<br>La voiture "..voiture.." est conduite par "..conducteur.."."
end
souffrant = table.remove(circuit,3)
reponse = reponse.."<br><u>voitures après le départ de "..souffrant.." de la voiture 3.</u>"
for voiture, conducteur in ipairs(circuit) do
reponse = reponse.."<br>La voiture "..voiture.." est conduite par "..conducteur.."."
end
return reponse
end
return p
En tapant {{#invoke:Tables|retrait}}, on obtient :
voitures au départ du circuit.
La voiture 1 est conduite par Laurent.
La voiture 2 est conduite par Cécile.
La voiture 3 est conduite par Alain.
La voiture 4 est conduite par Cloé.
La voiture 5 est conduite par Amandine.
voitures après le départ de Alain de la voiture 3.
La voiture 1 est conduite par Laurent.
La voiture 2 est conduite par Cécile.
La voiture 3 est conduite par Cloé.
La voiture 4 est conduite par Amandine.
table.concat
[modifier | modifier le wikicode]La fonction table.concat permet d'obtenir une chaîne de caractères exposant le contenu (ou une partie du contenu) d'une table. Elle s'utilise sous la forme : table.concat(table, séparateur, début, fin)
Les paramètres séparateur, début et fin sont facultatifs. Si le paramètre séparateur est absent, il n'y aura pas de séparateurs. Si le paramètre début est absent, la concaténation se fera à partir du début de la table. Si le paramètre fin est absent, la concaténation se fera jusqu'à la fin de la table.
Écrivons, en exemple, une fonction qui nous donne les conducteurs des exemples précédents sauf le premier.
local p = {}
local circuit = {"Laurent","Cécile","Alain","Cloé","Amandine"}
function p.conducteurs()
return table.concat( circuit, " et aussi ",2,5)
end
return p
En tapant {{#invoke:Tables|conducteurs}}, on obtient : Cécile et aussi Alain et aussi Cloé et aussi Amandine
table.maxn
[modifier | modifier le wikicode]Retourne le plus grand index numérique positif utilisé dans la table.
local p = {}
local circuit = {"Laurent","Cécile","Alain","Cloé","Amandine"}
function p.sup()
return table.maxn(circuit)
end
return p
En tapant {{#invoke:Tables|sup}}, on obtient : 5
table.sort
[modifier | modifier le wikicode]Permet de trier la table.
local p = {}
local circuit = {"Laurent","Cécile","Alain","Cloé","Amandine"}
function p.trie()
table.sort(circuit)
return "le nouvel ordre est : "..table.concat(circuit,", ")
end
return p
En tapant {{#invoke:Tables|trie}}, on obtient : le nouvel ordre est : Alain, Amandine, Cloé, Cécile, Laurent
Nous voyons que les éléments de la table ont été triés par ordre alphabétique.
Fonctions concernant le système d'exploitation
[modifier | modifier le wikicode]
Les exemples donnés dans ce paragraphe se trouvent dans le Module:Temps.
os.time
[modifier | modifier le wikicode]cette fonction nous ramène un nombre mystérieux à 10 chiffres qui probablement contient, sous forme codé la date et l’heure.
local p = {}
function p.heure()
return "La fonction os.time nous retourne : "..os.time()
end
return p
{{#invoke:Temps|heure}} nous retourne : La fonction os.time nous retourne : 1733626290
os.difftime
[modifier | modifier le wikicode]Cette fonction semble être destinée à faire la différence entre deux temps donnée sous un format particulier. En tout cas, elle fait la différence entre deux nombres.
local p = {}
function p.delais()
return "La fonction os.difftime nous retourne : "..os.difftime(74,3)
end
return p
{{#invoke:Temps|delais}} nous retourne : La fonction os.difftime nous retourne : 71
os.clock
[modifier | modifier le wikicode]Cette fonction donne le temps CPU d'exécution d'un programme en seconde. Cette fonction permet donc de mesurer le temps qui s'écoule entre deux parties d'un programme. Pour des exemples d'application voir, dans le chapitre sur la gestion de l'environnement, le paragraphe sur la vitesse d'exécution d'un programme. Voir aussi l'exercice 7-1.
Le temps CPU démarre au moment où commence à s'afficher la page dans laquelle se trouve le module contenant la fonction os.clock et pas au moment où est lancé le module. Par conséquent, si dans une page, on met plusieurs modules utilisant la fonction os.clock, on observera un temps d'autant plus grand que le module est près du bas de la page.
Le temps CPU est donné en seconde et est le plus souvent compris entre 2 et 20 millisecondes.
Si l’on veut chronométrer le temps d'exécution d'une partie d'un programme, on est obligé de mettre l'instruction avant et après la partie à chronométrer et de faire la différence entre les deux temps obtenus. On obtient alors un temps généralement très inférieur à la milliseconde.
Compte tenu du fait que nos programmes s'exécutent sur un système multitâche, le temps donné par la fonction os.clock est très approximatif et sera différent d'une exécution à l'autre. |
Si dessous nous présentons un exemple donnant le temps écoulé, depuis le début de l’affichage de cette page, en millisecondes :
local p = {}
function p.horloge()
local reponse = ""
local temps = os.clock()
reponse = reponse.. "<br>La fonction os.clock nous retourne : "..temps
temps = temps*1000
reponse = reponse.."<br>Ce qui signifie que "..temps.." millisecondes se sont écoulées depuis le début de l’affichage de cette page."
return reponse
end
return p
{{#invoke:Temps|horloge}} nous donne :
La fonction os.clock nous retourne : 0.54356
Ce qui signifie que 543.56 millisecondes se sont écoulées depuis le début de l’affichage de cette page.
os.date
[modifier | modifier le wikicode]Cette fonction permet de donner la date et l’heure sous un format choisi. Elle peut fournir une chaîne de caractère ou remplir un tableau avec les différents éléments de la date et de l’heure.
local p = {}
function p.date()
return "La fonction os.date nous retourne : "..os.date()
end
return p
{{#invoke:Temps|date}} nous retourne : La fonction os.date nous retourne : Sun Dec 8 02:51:30 2024
Fonctions concernant la librairie Scribunto
[modifier | modifier le wikicode]
Les exemples donnés dans ce paragraphe se trouvent dans le Module:Scribunto
mw.clone
[modifier | modifier le wikicode]Nous savons que les tables et les fonctions sont affectées par référence. Si A est une table, l'instruction B = A affectera à B l'adresse de la table A et toutes les modifications effectuées sur A sembleront se répercuter sur B. Pour réaliser une véritable recopie du contenu de la table A dans la table B, on utilisera la fonction préprogrammée mw.clone en écrivant B = mw.clone(A). Cette fois, les tables A et B seront totalement indépendantes bien que contenant les mêmes objets juste après l'affectation.
Dans l'exemple ci-dessous, nous vérifions ce que nous venons de dire en créant une table A que nous affectons à une table B grâce à B = A et à une table C grâce à C = mw.clone(A). Nous modifions ensuite A et nous vérifions que cette modification s'est bien répercutée sur B mais pas sur C.
local p = {}
function p.duplique()
local A = {"truc", "machin", "chose"}
local B = A
local C = mw.clone(A)
A[2] = "bidule"
return "B[2] contient "..B[2].." et C[2] contient "..C[2]
end
return p
{{#invoke:Scribunto|duplique}} nous donne : B[2] contient bidule et C[2] contient machin
mw.loadData
[modifier | modifier le wikicode]Cette fonction permet de charger une table (ou plusieurs) se trouvant dans un autre module. Cette table contient en général un grand nombre de données. Plusieurs raisons justifient le fait que cette table soit écrite dans un autre module :
- Elle est susceptible d’être utilisée par plusieurs modules.
- Cette table n'est chargée qu'une seule fois par page, même si celle-ci utilise plusieurs commandes #invoke, ce qui entraîne une économie de temps.
Certaines restrictions doivent être respectées :
- On ne peut pas charger autre chose qu'une table.
- La table que l’on charge ne doit pas contenir de fonctions.
- Les fonctions pairs et ipairs fonctionnent sur la table retournée. Le fonctionnement des autres fonctions spécialisées dans les tables n’est pas garanti.
Dans l'exemple ci-dessous, la fonction codehex permet de convertir un nom de couleur (de la page Liste de couleurs) en son code hexadécimal qu'elle retourne.
La fonction mw.loadData de l'exemple ci-dessous charge une table se trouvant dans le Module:Nomcouleur. Dans le Module:Nomcouleur, la table chargée se nomme nuancier. On remarquera, en fin de module, l'instruction return nuancier pour que la table puisse être chargée par la fonction mw.loadData à partir d'un autre module.
local p = {}
function p.codehex(frame)
local code = mw.loadData("Module:Nomcouleur")
local couleur = frame.args[1]
return "Le code hexadécimal correspondant à la couleur "..couleur.." est "..code[couleur]
end
return p
Nous voyons, dans l'exemple ci-dessus, que la fonction mw.loadData attend un argument sous forme de chaîne de caractères. Nous lui avons donc donné le nom du module contenant la table à charger, à savoir "Module:Nomcouleur", sans oublier les guillemets et le mot module.
{{#invoke:Scribunto|codehex|caca d'oie}} nous retourne : Le code hexadécimal correspondant à la couleur caca d'oie est cdcd0d
Nota : Si l’on souhaite charger plusieurs tables s'appelant, par exemple, A, B, C à l'aide de la fonction mw.loadData, il faut que dans le module appelé, il y ait l'instruction return {A, B, C}. En fait, on retourne une table de tables.
mw.allToString
[modifier | modifier le wikicode]La fonction mw.allTostring convertit en chaînes de caractères tous ses arguments et concatène les chaînes de caractères obtenues en les séparant par une tabulation. Dans l'exemple, ci-dessous, nous convertissons et concaténons des arguments de types différents : Le nombre 2, la chaîne "mouche", le type nil et le booléen true.
local p = {}
function p.converti()
return mw.allToString(2,"Mouche",nil,true)
end
return p
{{#invoke:Scribunto|converti}} nous retourne : 2 Mouche nil true
Nous vérifierons en exercice qu’il y a bien une tabulation comme séparateur.
mw.getCurrentFrame
[modifier | modifier le wikicode]Cette fonction retourne l’objet frame courant. Elle permet donc, par exemple, une recopie de l’objet frame actuel. Dans l'exemple qui suit, l’objet frame a été recopié dans l’objet frami qui peut s'utiliser comme l’objet frame. Un des avantages est que l’utilisation du nouvel objet sera plus rapide car ne nécessitant plus un appel extérieur au programme.
En réalité, l'explication que nous venons de donner est très fragmentaire car nécessitant des éléments que nous n'avons pas encore étudiés. Nous reviendrons donc sur l'étude de cette fonction dans le chapitre consacré à l’objet frame pour essayer d’en donner une explication plus complète.
Pour le moment, nous nous contenterons de l'exemple suivant ou l’objet frame est recopié dans l’objet frami. On visualise ensuite le contenu de l’objet frami comme on le ferait pour l’objet frame.
local p = {}
function p.courant(frame)
frami = mw.getCurrentFrame()
reponse = ""
for i = 1,5 do
reponse = reponse.."<br />à la clé "..i..", on trouve : "..frami.args[i]
end
return reponse
end
return p
{{#invoke:Scribunto|courant|Marmite|17|Tuile|5|Maison}} nous retourne :
à la clé 1, on trouve : Marmite
à la clé 2, on trouve : 17
à la clé 3, on trouve : Tuile
à la clé 4, on trouve : 5
à la clé 5, on trouve : Maison
mw.incrementExpensiveFunctionCount
[modifier | modifier le wikicode]
mw.log
[modifier | modifier le wikicode]
Fonctions concernant le chargement (Librairie Package)
[modifier | modifier le wikicode]
Les exemples donnés dans ce paragraphe se trouvent dans le Module:Package
require
[modifier | modifier le wikicode]Cette fonction sera étudiée dans le chapitre sur la gestion de l'environnement.
package.loaders
[modifier | modifier le wikicode]
package.preload
[modifier | modifier le wikicode]
package.seeall
[modifier | modifier le wikicode]
Gestion de l'environnement
Dans ce chapitre, nous allons étudier quelques aspects plus liés avec l'utilisateur et l'intégration du Lua dans un des projets wikimédia, c'est-à-dire Scribunto. Nous commencerons par la gestion des erreurs. Nous verrons ensuite quelques commandes utiles permettant d’utiliser un module dans le but de réaliser une certaine fonction.
Gestion des erreurs
[modifier | modifier le wikicode]Dans ce paragraphe, nous allons dire quelques mots sur la gestion des erreurs.
Exposé du problème
[modifier | modifier le wikicode]Lorsqu'on fait un programme pour notre usage personnel, on sait généralement ce qu’il contient et on peut alors l’utiliser correctement. Si ce programme est destiné à être utilisé par quelqu’un d'autre, alors on n’est pas sûr que l'autre utilisateur aura bien compris comment marche le programme. L'utilisateur va, peut-être, faire des erreurs en l'utilisant. Le bon programmeur doit être capable d'anticiper toutes les erreurs qu'un utilisateur peut faire et doit prévoir, dans son programme, des instructions pour permettre au programme de réagir correctement en cas d'erreur d'utilisation.
Prenons un exemple :
Reprenons la fonction p.alerte4 que nous avons écrite au troisième chapitre
local p = {}
function p.alerte4(frame)
local poids = tonumber(frame.args[1])
local reponse
if poids < 55 then
reponse = "Votre poids est acceptable"
else
if poids < 60 then
reponse = "Attention, vous commencez à grossir !"
else
reponse = "Grosse vache !!"
end
end
return reponse
end
return p
Nous avons testé cette fonction en écrivant :
{{#invoke:Balance|alerte4|56}}, et nous avons obtenu : Attention, vous commencez à grossir !
Supposons maintenant que l'utilisatrice, pour qui nous avons écrit cette fonction, n'a pas bien compris comment marche cette fonction et écrive le nombre 56 en toute lettre :
{{#invoke:Balance|alerte4|cinquante-six}}, elle obtiendra alors : Erreur Lua dans Module:Balance à la ligne 35 : attempt to compare nil with number.
Que c'est-il passé. En fait, rien de bien compliqué, nous avons vu, dans le chapitre exposant les fonctions préprogrammées de base, que la fonction tonumber ne comprenait pas le français et était incapable de convertir la chaîne de caractère "cinquante-six" en nombre 56. Et dans ce cas là, elle nous retourne nil. par conséquent la variable poids contenait nil au moment où elle a été comparée au nombre 55, ce qui a provoqué l'erreur de script.
Gestion programmée de l'erreur
[modifier | modifier le wikicode]Comment remédier à ce problème ? Il nous suffit simplement d’éviter que la variable poids soit comparée à un nombre lorsqu'elle contient nil et que dans ce cas la fonction nous retourne un message d'erreur informant l'utilisatrice de l'erreur qu'elle a commise. Nous devons donc encore perfectionner notre programme en écrivant une nouvelle fonction p.alerte5 qui met en œuvre ce que l’on vient de dire :
local p = {}
function p.alerte5(frame)
local poids = tonumber(frame.args[1])
local reponse
if poids == nil then
reponse = "Vous n'avez pas rentré un nombre sous un format reconnaissable !"
else
if poids < 55 then
reponse = "Votre poids est acceptable !"
else
if poids < 60 then
reponse = "Attention, vous commencez à grossir !"
else
reponse = "Grosse vache !!"
end
end
end
return reponse
end
return p
Testons cette nouvelle fonction :
En écrivant :
{{#invoke:Balance|alerte5|56}}, nous obtenons : Attention, vous commencez à grossir !
Et en écrivant :
{{#invoke:Balance|alerte5|cinquante-six}}, nous obtenons : Vous n'avez pas rentré un nombre sous un format reconnaissable !
Nous voyons que nous obtenons une réponse appropriée dans tous les cas de figure.
Gestion à l'aide de pcall
[modifier | modifier le wikicode]Une autre façon de gérer les erreurs pouvant se produire à l'appel d'une fonction est d’utiliser la fonction pcall dont le rôle est justement de gérer les erreurs à l'appel d'une fonction. Il suffit d'invoquer la fonction en lui donnant comme paramètres : le nom de la fonction, ses paramètres et le message d'erreur s'il y a problème.
Pour tester cette fonction écrivons, dans le Module:Balance une nouvelle fonction p.alerte6 ainsi :
local p = {}
function p.alerte6(frame)
local poids = pcall(tonumber,frame.args[1],"Vous n'avez pas rentré un nombre sous un format reconnaissable !")
local reponse
if poids < 55 then
reponse = "Votre poids est acceptable"
else
if poids < 60 then
reponse = "Attention, vous commencez à grossir !"
else
reponse = "Grosse vache !!"
end
end
return reponse
end
return p
Testons pour voir si ça marche :
En écrivant :
{{#invoke:Balance|alerte6|56}}, nous obtenons : Attention, vous commencez à grossir !
Et en écrivant :
{{#invoke:Balance|alerte6|cinquante-six}}, nous obtenons : Vous n'avez pas rentré un nombre sous un format reconnaissable !
Rapidité d'exécution d'un programme
[modifier | modifier le wikicode]En informatique, un programme, bien qu’il s'exécute correctement, peut ne pas donner entièrement satisfaction si son exécution ne se fait pas assez rapidement. Nous n'avons pas évoqué le problème jusqu'à maintenant, mais le programmeur doit garder présent à l'esprit que son programme doit s'exécuter le plus rapidement possible pour éviter de trop monopoliser la machine qui exécute son programme. Bien souvent, pour exécuter une tache particulière, plusieurs solutions s'offrent à nous. Nous devons alors choisir, parmi toutes les solutions possibles, celle dont le temps d’exécution sera le plus bref possible. Prenons un exemple :
Supposons que nous voulions écrire une fonction qui nous renvoie la valeur du polynôme p(x) = 7x4+5x3+3x2+x+2. Une première façon d'écrire cette fonction pourrait être :
local p = {}
function p.poly(frame)
return 7*frame.args[1]^4+5*frame.args[1]^3+3*frame.args[1]^2+frame.args[1]+2
end
return p
Cette façon d'écrire le programme, bien que fonctionnant parfaitement, n’est pas correcte. Pourquoi ?
En fait, lorsqu'on écrit frame.args[1], on fait appel à la valeur de x que nous a fourni l'utilisateur dans la commande #invoke et qui est donc quelque chose d'extérieur au programme. Allez chercher cette valeur met en œuvre des routines qui sont, à elles seules, des programmes qu’il faut appeler et exécuter. tout cela demande du temps. On comprend donc aisément qu'écrire quatre fois frame.args[1] va demander un temps d'exécution plus long que si l’on ne l'écrivait qu'une seule fois. Il est donc bien préférable d'écrire la fonction en s'arrangeant pour n'avoir qu'une seule exécution de frame.args[1]. À la première façon d'écrire le programme, nous préférerons donc la deuxième façon suivante :
local p = {}
function p.poly(frame)
local x = frame.args[1]
return 7*x^4+5*x^3+3*x^2+x+2
end
return p
On pourrait, à ce niveau, être satisfait, de notre programme et penser que l’on a la meilleure façon possible de l'écrire. En fait, il n'en est rien ! Il est encore possible d'améliorer le temps d'exécution de la fonction p.poly. Cette façon d'écrire un polynôme, en informatique, n’est pas correcte. Si nous comptons les multiplications et les additions, nous voyons qu’il y a 9 multiplications et 4 additions. Peut-on imaginer une façon d'écrire un polynôme de façon à réduire le nombre d'opérations. Cela est possible en remarquant, tout simplement, que :
Si nous comptons le nombre de multiplication et d'addition dans l’expression x(x(x(7x+5)+3)+1)+2, nous voyons qu’il y a 4 multiplications et 4 additions. Nous avons 5 multiplications de moins à faire dans la calcul de x(x(x(7x+5)+3)+1)+2 que dans la calcul de 7x4+5x3+3x2+x+2, d'où le gain de temps. Nous pouvons donc améliorer encore le temps d'exécution de la fonction p.poly en l'écrivant :
local p = {}
function p.poly(frame)
local x = frame.args[1]
return (((7*x+5)*x+3)*x+2)*x+1
end
return p
Nous pouvons ainsi espérer avoir la meilleure façon possible d'écrire notre fonction !
La question qui peut maintenant venir à l'esprit est la suivante : Supposons que l’on ait plusieurs façons d'écrire un programme, comment peut-on savoir laquelle de ces façons est la plus rapide ?
Pour cela, nous disposons d'une fonction préprogrammée qui se nomme os.clock et qui est capable de nous donner une approximation du temps d'exécution du programme que l’on est en train d'écrire. Nous allons donc, dans un Module:Polynôme essayer de comparer les temps d'exécution des trois façons d'écrire le programme de calcul de la fonction p.poly vue précédemment. En fait, nous allons écrire trois fonctions : p.poly1, p.poly2 et p.poly3 qui, en plus du résultat nous renvoie le temps d'exécution.
local p = {}
function p.poly1(frame)
local temps = os.clock()
local reponse = "Le résultat est "
reponse = reponse..7*frame.args[1]^4+5*frame.args[1]^3+3*frame.args[1]^2+frame.args[1]+2
temps = os.clock() - temps
reponse = reponse.." et le temps d'exécution est "..temps
return reponse
end
function p.poly2(frame)
local temps = os.clock()
local x = frame.args[1]
local reponse = "Le résultat est "
reponse = reponse..7*x^4+5*x^3+3*x^2+x+2
temps = os.clock() - temps
reponse = reponse.." et le temps d'exécution est "..temps
return reponse
end
function p.poly3(frame)
local temps = os.clock()
local x = frame.args[1]
local reponse = "Le résultat est "
reponse = reponse..(((7*x+5)*x+3)*x+1)*x+2
temps = os.clock() - temps
reponse = reponse.." et le temps d'exécution est "..temps
return reponse
end
return p
Nous remarquons que, pour chaque fonction, nous prenons le temps en début de fonction et en fin de fonction et nous faisons la différence des deux temps pour avoir une estimation du temps d'exécution de la fonction seule. Si nous ne faisions pas cela, nous aurions, à la fin de la troisième fonction, le temps cumulé des trois fonctions car le compteur temps ne s’arrête pas d'une fonction à l'autre.
{{#invoke:Polynôme|poly1|7}} nous donne : Le résultat est 18678 et le temps d'exécution est 4.000000000004e-05
{{#invoke:Polynôme|poly2|7}} nous donne : Le résultat est 18678 et le temps d'exécution est 2.000000000002e-05
{{#invoke:Polynôme|poly3|7}} nous donne : Le résultat est 18678 et le temps d'exécution est 2.000000000002e-05
Nous voyons maintenant clairement que la meilleure des trois fonctions est bien la troisième et la plus mauvaise est bien la première.
En fait, le temps d'exécution obtenu par cette méthode est très approximatif, car nous fonctionnons sur un système multitâche et l'exécution d'une fonction peut être perturbée selon la charge du processeur. Dans l'exécution ci-dessus, nous pouvons même avoir quelquefois un temps d'exécution, de la troisième fonction, supérieur à la seconde. Pour mieux se rendre compte de la rapidité d'exécution de chaque fonction, il est donc conseillé de purger le cache de la page plusieurs fois pour relancer plusieurs fois l'exécution des fonctions et ainsi mieux se rendre compte. |
Comment utiliser des fonctions écrites dans un autre module
[modifier | modifier le wikicode]Dans les chapitres précédents, nous avons dit qu'une variable ou une fonction déclarée avec le mot-clé local n'est utilisable qu’à l'intérieur du module où elles sont déclarées. Cette affirmation sous-entend que si l’on n'emploie pas le mot-clé local, alors la variable ou la fonction déclarées devrait pouvoir être utilisée dans un autre module. Dans ce paragraphe, nous allons donc étudier comment utiliser les variables et les fonctions créées dans un autre module sans le mot-clé local.
Le principal intérêt de cette possibilité va être de pouvoir se confectionner des modules contenant des fonctions qui peuvent être utiles dans plusieurs autres modules en évitant ainsi de devoir les réécrire dans chaque module.
La fonction préprogrammée qui va nous permettre d'appeler le contenu d'un autre module est la fonction require. Il y a deux façons d’utiliser la fonction require selon que l’on souhaite récupérer des objets, dans un autre module, qui ne se trouve pas dans la table que l’on a pris l'habitude d'appeler p (mais qui pourrait s'appeler autrement) ou que l’on souhaite récupérer des objets qui sont dans une table p.
Première façon : On souhaite récupérer des objets indépendants de la table p
[modifier | modifier le wikicode]Il est, bien sûr, indispensable que les objets que l’on souhaite récupérer ne soient pas déclarés en local.
Prenons un exemple : Dans le Module:Fonction, nous avions écrit une fonction f qui élève un nombre au carré. Dans un autre Module:Aspire, essayons d'y inclure le module Fonction et de créer une fonction carre qui appelle la fonction f pour élever un nombre au carré :
local p = {}
require("Module:Fonction")
function p.carre(frame)
return f(frame.args[1])
end
return p
La fonction require attend une chaîne de caractères. Nous avons donc dû mettre Module:Fonction entre guillemet. Ne pas oublier, aussi, le mot Module. Si l’on avait écrit require("Fonction"), nous aurions eu une erreur de script.
{{#invoke:Aspire|carre|7}} nous donne : 49
Nous avons bien obtenu 7 au carré qui donne 49. On peut aussi vérifier que l’on peut récupérer une variable déclarée dans le module que l’on appelle avec require a condition que cette variable ne soit pas déclarée avec le mot clé local, sinon on obtient une erreur de script.
Deuxième façon : L'objet que l’on souhaite récupérer est dans une table p
[modifier | modifier le wikicode]C'est un peu comme si l’on souhaitait utiliser la commande #invoke à partir d'un autre module. Mais cette commande ne marche pas si elle est utilisée dans un module. Nous allons donc transférer le contenu de la table p dans une table locale au module appelant.
Prenons un exemple : Dans un Module:Ingère écrivons une fonction compo qui appelle la fonction p.cube se trouvant dans le Module:Réservoir
Le contenu du module Réservoir est :
local p = {}
function p.cube(nombre)
return nombre^3
end
return p
La grosse différence avec ce que l’on avait l'habitude de voir est que l’on ne trouve pas frame entre les parenthèses de la fonction p.cube puisque, cette fois, nous n'avons pas l'intention d’utiliser la commande #invoke. À part cela, le reste est identique !
Question : Peut-on appeler, à partir d'un autre module, une fonction qui aurait frame entre parenthèse ? Réponse : Non, car frame implique obligatoirement l’utilisation de la commande #invoke. Et cette commande ne peut pas être utilisée dans un module. |
Le contenu du module Ingère est :
local p = {}
local t = require("Module:Réservoir")
function p.compo(frame)
local a = frame.args[1]
return t.cube(a)
end
return p
Le contenu de la table p du module Réservoir est transféré dans la table t du module Ingère. La fonction p.cube est devenue la fonction t.cube.
{{#invoke:Ingère|compo|3}} nous donne alors : 27
Priorité de l'interpréteur
[modifier | modifier le wikicode]En général, l'endroit le plus adéquat pour appeler un module est de le faire à partir d'un modèle. Ceci est fortement conseillé pour éviter de surcharger l'espace principal avec la commande #invoke. Par conséquent, le plus souvent, les modèles appelleront les modules. Quelquefois, on risque de devoir faire le contraire. C'est-à-dire d'appeler un modèle dans un module. Que se passe-t-il alors ? Nous allons tester cette opération en prenant un exemple. Essayons d'écrire un module qui aurait pour fonction d'encadrer un texte en faisant appel au Modèle:Encadre. Dans un Module:Cadre, nous serions tenté d'écrire une fonction p.cadre1 ainsi :
local p = {}
function p.cadre1(frame)
return "{{Encadre|contenu="..frame.args[1].."}}"
end
return p
{{#invoke:Cadre|cadre1|Coucou, je suis dans un cadre!}} nous donne alors : {{Encadre|contenu=Coucou, je suis dans un cadre!}}
Et là, avec un grand désarroi, nous constatons que cela ne marche pas. Que s'est-il passé ? En fait, l'interpréteur de mediawiki évalue les modèles avant d’avoir les retours des modules. Et, par conséquent, quand le module Cadre nous ramène {{Encadre|contenu=Coucou, je suis dans un cadre!}}, il est déjà trop tard !
Heureusement, la situation n’est pas désespérée car nous disposons, dans notre lua avec scribunto, d'une fonction préprogrammée frame:preprocess qui va évaluer les modèles avant que ceux-ci ne soient retournés. Pour expérimenter cela, nous allons donc écrire, dans le Module:Cadre, une nouvelle fonction p.cadre2, ainsi :
local p = {}
function p.cadre2(frame)
return frame:preprocess("{{Encadre|contenu="..frame.args[1].."}}")
end
return p
{{#invoke:Cadre|cadre2|Coucou, je suis dans un cadre!}} nous donne enfin :
Coucou, je suis dans un cadre! |
Et là, ça marche, youpi !
Méta-tables
Dans ce chapitre, nous allons étudier les méta-tables. La tradition voudrait que l’on explique les méta-tables de façon hermétique pour que personne n'y comprenne rien (voir, par exemple, mw:Extension:Scribunto/Lua reference manual/fr, paragraphe sur les méta-tables). Nous allons essayer toutefois, dans ce chapitre, de déroger à la tradition, quitte à s'attirer les foudres de ceux qui voudraient que cette partie reste réservée à de rares initiés.
Position du problème
[modifier | modifier le wikicode]Si l’on se pose la question : "Quels sont les objets qui sont le plus susceptibles de provoquer une erreur de script ?", la réponse serait, tout naturellement "les tables". En effet, si l’on nous demande de provoquer une erreur de script, en utilisant des tables, nous n'avons que l'embarras du choix. On peut y arriver en utilisant une clé inexistante dans la table et concaténer le retour (nil) avec une chaîne de caractères. On peut essayer d'additionner deux tables t1 et t2 (t1 + t2). On peut essayer de les multiplier, les soustraire, les diviser, les concaténer, etc. Bref, aucune opération n'est possible avec les tables ! Pourtant, si l’on programme dans un domaine particulier comme, par exemple, les mathématiques, on aimerait bien pouvoir effectuer certaines opérations directement sur les tables sans provoquer d'erreur de script.
Prenons un exemple : En mathématique, on peut représenter les coordonnées d'un point, dans un repère, par une matrice colonne et si l’on souhaite faire un programme réalisant certains calculs sur les points dans l'espace, on va naturellement ranger les coordonnées d'un point dans une table avec clé numérique ou pas.
Si le point B a pour coordonnées (3,7) et le point C a pour coordonnée (1,8), on écrira
avec clé numérique:
local B = {3,7}
local C = {1,8}
avec clé sous forme de chaîne de caractères :
local B = {["abscisse"] = 3, ["ordonnée"] = 7}
local C = {["abscisse"] = 1, ["ordonnée"] = 8}
Si, dans notre programme, on souhaite calculer le milieu I du segment [BC], on aimerait bien écrire :
Mais ce n’est pas possible car B + C provoque une erreur de script.
Les concepteurs du Lua ont donc eu l’idée de rendre possible les opérations sur les tables qui provoquait, par exemple, une erreur de script. Mais comme une même opération peut ne pas opérer de la même façon sur les tables selon le domaine dans lequel on programme ou selon ce que contient la table, il va falloir indiquer, pour chaque table, comment cette opération devra agir. La méthode opératoire que l’on va programmer et qui permettra à une opération (qui avant provoquait une erreur de script ou un effet particulier) de devenir valide sera appelée méta-méthode. La méta-méthode de la multiplication peut, par exemple, être différente selon que l’on multiplie des tables contenant des coordonnées (produit scalaire de deux vecteurs) ou que l’on multiplie des tables contenant des matrices (produit matriciel). Les méta-méthodes seront donc mémorisées dans des tables que l’on associera aux tables utilisant ces méta-méthodes. Une table ainsi associée à une autre table sera appelée méta-table.
Nous retiendrons qu'une méta-table, c’est déjà une table (comme les autres tables) mais que l’on va associer à d'autres tables grâce à une fonction appelée setmetatable. Par exemple si l’on appelle a et b deux tables et si l’on veut que b soit la méta-table de a, on écrira :
setmetatable(a,b)
Si la table b contient une méta-méthode décrivant ce que doit donner l'opérateur + si on l'applique à deux tables, alors on pourra se permettre d'écrire :
c = a + d
sans que cela ne provoque une erreur de script.
On remarque, dans l'exemple ci-dessus, que l’on a défini b comme étant la méta-table de a mais pas celle de d. Ce n’est pas grave, il suffit que la méta-méthode soit définie dans la méta-table de l'une des deux tables a ou d pour que cela marche.
Plus précisément, pour les opérateurs addition, soustraction, multiplication, division, puissance, modulo et concaténation, le Lua commence par chercher la méta-table de la table avant l'opérateur et regarde si celle-ci possède une méta-méthode concernant l'opérateur. Dans le cas contraire, le Lua cherche la méta-table de la table écrite après l'opérateur ainsi qu'une méta-méthode appropriée. Nous voyons, par conséquent, qu'une méta-méthode dans l'opérateur de gauche est prioritaire.
La façon de procéder sera, par contre, différente pour les opérateurs relationnels comme inférieur, supérieur et égal ou chaque table concernée devra avoir une méta-table avec la même méta-méthode.
Si l’on souhaite obtenir la méta-table d'une table, nous utiliserons la fonction getmetatable qui retourne la méta-table d'une table passée en paramètre (sauf s'il existe une méta-méthode disant de retourner autre chose).
Comment programmer une méta-méthode
[modifier | modifier le wikicode]Une méta-méthode, c’est tout simplement une fonction. Nous savons que les tables peuvent contenir des fonctions. Programmer une méta-méthode revient donc à créer une fonction que l’on va mémoriser dans une méta-table. Il y a juste une petite règle à respecter. Il faut que la chaîne de caractère qui sert de clé pour indexer la fonction décrivant une méta-méthode commence par deux soulignées comme, par exemple, __index. Prenons un premier exemple simple pour comprendre comment procéder :
Nous savons que si l’on essaye d'accéder, dans une table, à une clé qui n'existe pas, nous obtenons nil. Nous allons essayer de changer ceci par une méta-méthode qui fera en sorte, qu'au lieu d'obtenir nil, on obtienne le message "(La clé spécifiée n'existe pas dans cette table)"
Nous écrirons le programme suivant qui se trouve dans le Module:Méta
local p = {}
local t = {"Piano","Boule","Tortue","Nénuphar"}
local mt = {}
function mt.__index()
return "(La clé spécifiée n'existe pas dans cette table)"
end
setmetatable(t,mt)
function p.mtable()
local reponse = " "
for i = 1,5 do
reponse = reponse.."<br>a la clé "..i..", on trouve : "..t[i]
end
return reponse
end
return p
En tapant {{#invoke:Méta|mtable}}, on obtient :
a la clé 1, on trouve : Piano
a la clé 2, on trouve : Boule
a la clé 3, on trouve : Tortue
a la clé 4, on trouve : Nénuphar
a la clé 5, on trouve : (La clé spécifiée n'existe pas dans cette table)
Sans méta-méthode se trouvant dans la méta-table mt associé à t, le programme précédent aurait provoqué une erreur de script car on essaye de concaténer t[5] qui n'existe pas (et qui vaut donc nil) à une chaîne de caractère.
On pourrait le vérifier en supprimant simplement l'instruction setmetatable(t,mt)
Si nous analysons le programme précédent, nous voyons qu’il n'y a rien de bien sensationnel si ce n'est la fonction :
function mt.__index()
return "(La clé spécifiée n'existe pas dans cette table)"
end
qui demande quelques explications.
Tout d’abord, comme nous l'avons vu dans les chapitres précédents, mt.__index() pourrait s'écrire mt["__index"]() pour mettre en évidence le fait que __index est une clé sous forme de chaîne de caractères permettant l'accès ici à une fonction se trouvant dans la table mt. Comme nous avons affaire à une méta-table, on ne dira pas que __index est une clé mais on dira que __index est un champ. Comme cela, lorsque l’on parlera de champ, on saura que l’on a affaire à une clé d'une méta-table. Attention toutefois, si l’on parle généralement de champ quand il s'agit d'une méta-table, il peut arriver que l’on utilise le mot champ au lieu du mot clé quand il s'agit d'une table ordinaire, mais c’est plus rare.
Ensuite, une question vient tout de suite à l'esprit. Comment sait-on que mt.__index est une méta-méthode concernant le comportement à adopter si l’on essaye d'accéder à une table avec une clé qui n'existe pas. En fait, les champs des méta-méthodes sont déjà préprogrammés dans le Lua. Si l’on utilise une clé qui n'existe pas dans une table, le Lua (avant de retourner erreur de script) va automatiquement commencer par regarder si la table concernée possède une méta-table et, si c’est le cas, va regarder ce qui se trouve, dans cette méta-table, à la clé __index (champ __index). Nous verrons dans un prochain exemple que si nous voulions additionner deux tables, le Lua chercherait automatiquement une méta-table associée ayant un champ se nommant __add.
Étude des champs d'une méta-table
[modifier | modifier le wikicode]Nous avons vu précédemment que le Lua avait prédéfini des champs particuliers (comme __index) pouvant être utilisés dans une méta-table pour indexer des méta-méthodes définissant le comportement particulier de la table à laquelle la méta-table est associée. Nous allons, dans ce paragraphe, passer en revue les principaux champs en essayant de donner, pour chacun d'eux, un exemple d'utilisation.
Tous les exemples se trouvent dans le Module:Champ.
Avant de commencer, nous pouvons faire une petite remarque. Il se peut que certaines méta-méthodes ne concernent qu'une seule table. Dans ce cas-là, il n’est pas nécessaire de placer la méta-méthode dans une autre table que la table concernée. On peut placer la méta-méthode dans la table concernée en la déclarant comme étant sa propre méta-table. Par exemple, pour déclarer que la table a est sa propre méta-table, on écrira setmetatable(a,a).
Le champ __index
[modifier | modifier le wikicode]Nous avons commencé à décrire ce champ sommairement plus haut pour simplifier l'exposé. Nous y revenons ici pour compléter notre description. Nous avons vu que la méta-méthode indexée par ce champ était une fonction. Dans l'exemple que nous avons donné, la fonction en question n'avait pas d'arguments.
Toutefois, il est possible d'y rajouter des arguments. Supposons que, dans le message que la méta-méthode retourne, nous voulions donner plus de précision au sujet de la clé qui n'existait pas dans la table. Il nous faut alors passer en argument la table ainsi que la clé fautive (la table est nécessaire car une méta-table peut être associée à plusieurs tables).
Le programme vu précédemment peut ainsi être amélioré :
local p = {}
local t = {"Piano","Boule","Tortue","Nénuphar"}
local mt = {}
function mt.__index(tab,cle)
return "(la clé "..cle .." n'existe pas dans cette table.)"
end
setmetatable(t,mt)
function p.vindex()
local reponse = " "
for i = 1,5 do
reponse = reponse.."<br>a la clé "..i..", on trouve : "..t[i]
end
return reponse
end
return p
tab reçoit le contenu de la table (par référence) et est donc de type table. cle reçoit la clé qui a posé problème. Nous pouvons donc, dans le retour, spécifier la clé qui a posé problème.
En tapant {{#invoke:Champ|vindex}}, on obtient :
a la clé 1, on trouve : Piano
a la clé 2, on trouve : Boule
a la clé 3, on trouve : Tortue
a la clé 4, on trouve : Nénuphar
a la clé 5, on trouve : (la clé 5 n'existe pas dans cette table.)
Question : Supposons que l’on ait prévu une méta-méthode en cas d'accès à une table avec un index qui n'existe pas et supposons qu’à titre exceptionnel, on souhaite que l'accès à la même table avec une clé qui n'existe pas retourne à nouveau nil dans un cas particulier. Est-ce possible ? Réponse : Oui, c’est possible il existe une fonction rawget telle que rawget(tab,k) est équivalent à tab[k] sauf que pour la fonction rawget, la méta-méthode est ignorée. On pourra donc utiliser cette fonction si l’on veut que l'accès à une table avec un index inexistant se comporte comme si l’on n'avait pas de méta-table. |
Le champ __add
[modifier | modifier le wikicode]Nous allons voir dans ce paragraphe comment additionner deux tables. Supposons que l’on ait deux tables B et C contenant les coordonnées de deux points B et C et supposons que nous voulions trouver un point A dont les coordonnées (mémorisées dans une table A) seront la somme des coordonnées de B et C. Par exemple, si B a pour coordonnées (3,7) et C a pour coordonnées (1,9), alors les coordonnées de A devront être (4,16).
Bien sûr, une façon simple de faire cela serait d'écrire :
local p = {}
local B = {3,7}
local C = {1,9}
function p.vadd()
local A = {}
A[1] = B[1] + C[1]
A[2] = B[2] + C[2]
return "Le point A à pour coordonnées "..A[1].." et "..A[2]
end
return p
et ça marche !
Le problème, c’est que l’on risque d’avoir toute une suite de calculs longs et fastidieux à faire et l’on aimerait bien simplifier l'écriture du programme ainsi :
local p = {}
local B = {3,7}
local C = {1,9}
function p.vadd()
local A = {}
A = B + C
return "Le point A à pour coordonnées "..A[1].." et "..A[2]
end
return p
Mais là, nous obtenons une erreur de script car B + C n’est pas défini a priori !
Que faire ? En fait, c’est très simple. Nous allons tout d’abord déclarer une table que nous appellerons point. Nous allons ensuite rattacher cette table aux tables B et C en tant que méta-table grâce aux instructions setmetatable(B,Point) et setmetatable(C,Point). On remarque au passage l'aspect naturel de ces deux déclarations car cela revient à définir A et B comme étant des points. Ensuite, nous allons mettre dans la table point une méta-méthode indiquant comment additionner les tables ayant la table point comme méta-table. Autrement dit nous allons définir dans la table point une fonction Point.__add.
Nous compléterons donc le programme précédent ainsi :
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__add(s,t)
local u = {}
u[1] = s[1] + t[1]
u[2] = s[2] + t[2]
return u
end
function p.vadd()
local A = {}
A = B + C
return "Le point A à pour coordonnées "..A[1].." et "..A[2]
end
return p
En tapant {{#invoke:Champ|vadd}}, on obtient : Le point A à pour coordonnées 4 et 16
Nous voyons que ça marche ! Nous n'avons plus d'erreur de script !
Le champ __sub
[modifier | modifier le wikicode]Ce champ permet de décrire comment faire la différence de deux tables. L'utilisation est similaire au champ __add, il suffit de remplacer + par -. Nous écrirons :
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__sub(s,t)
local u = {}
u[1] = s[1] - t[1]
u[2] = s[2] - t[2]
return u
end
function p.vsub()
local A = {}
A = B - C
return "Le point A à pour coordonnées "..A[1].." et "..A[2]
end
return p
En tapant {{#invoke:Champ|vsub}}, on obtient : Le point A à pour coordonnées 2 et -2
Le champ __mul
[modifier | modifier le wikicode]Ce champ permet de décrire comment multiplier deux tables entre elles. Si on n'avait guère de choix quand il s'agissait d'additionner ou de soustraire deux tables, la situation est différente pour la multiplication. En effet, ne serait-ce que dans le domaine des mathématiques, on peut envisager plusieurs façons de multiplier deux tables. Si les tables contiennent les coordonnées des vecteurs, on peut envisager d’en faire le produit scalaire ou le produit vectoriel. Si les tables mémorisent des matrices, on peut envisager d’en faire le produit matriciel. Plus généralement, si les tables sont des tenseurs, on peut faire des produits tensoriels. Nous voyons que la notion de méta-méthode acquiert une certaine importance dans le cas de la multiplication de deux tables. Dans ce paragraphe, nous verrons comment programmer une méta-méthode faisant le produit scalaire de deux vecteurs (nous étudierons le produit vectoriel et le produit matriciel en exercice).
Le produit scalaire de deux vecteurs de coordonnés (a,b) et (c,d) est le nombre ac+bd. Autrement dit c’est la somme du produit des abscisses et du produit des ordonnées. |
Nous allons donc créer une méta-méthode réalisant cette opération.
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__mul(s,t)
local p
p = s[1]*t[1]+s[2]*t[2]
return p
end
function p.scalaire()
local sca
sca = B*C
return "Le produit scalaire des vecteurs B et C est "..sca
end
return p
Dans ce dernier programme, nous remarquons une particularité intéressante, c’est que le produit de deux tables ne nous a pas donné une table mais un nombre. Avec les méta-méthodes, nous pouvons faire en sorte qu'une opération entre deux tables nous donne quelque chose qui n’est pas une table.
En tapant {{#invoke:Champ|scalaire}}, on obtient : Le produit scalaire des vecteurs B et C est 66
Le champ __div
[modifier | modifier le wikicode]Nous serions tenté de dire : "Dans ce paragraphe, nous allons voir comment diviser deux tables". En fait, il est rare d’avoir à diviser deux tables entre elles. Par conséquent, nous allons plutôt voir dans ce paragraphe comment diviser une table par un nombre, ce qui est plus utile comme par exemple dans l'opération vue plus haut dans ce chapitre.
C'est-à-dire que nous allons enfin voir, à titre d'exemple, comment calculer le milieu de deux points.
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__add(s,t)
local u = {}
u[1] = s[1] + t[1]
u[2] = s[2] + t[2]
return u
end
function Point.__div(s,a)
local q = {}
q[1] = s[1]/a
q[2] = s[2]/a
return q
end
function p.milieu()
local I = {}
local S = {}
S = B + C
setmetatable(S,Point)
I = S/2
return "Le milieu des deux points B et C a pour coordonnées "..I[1].." et "..I[2]
end
return p
En tapant {{#invoke:Champ|milieu}}, on obtient : Le milieu des deux points B et C a pour coordonnées 2 et 8
Nous avons écrit : local I = {}
local S = {}
S = B + C
: setmetatable(S,Point)
I = S/2
Alors que le bon sens nous aurait plutôt poussé à écrire : local I = {}
local S = {}
setmetatable(S,Point)
S = B + C
I = S/2
Pourtant, cette dernière façon d'écrire le programme provoque une erreur de script comme si l'opération S = B + C faisait perdre le fait que S a pour méta-table Point.
|
Le champ __concat
[modifier | modifier le wikicode]Dans ce paragraphe, nous allons voir comment concaténer deux tables. Là aussi, nous pouvons imaginer plusieurs façons de concaténer deux tables. Nous allons en voir une ici, à titre d'exemple, et nous en verrons une autre dans la page d'exercices associée.
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__concat(s,t)
local q = {}
q[1] = tonumber(s[1]..t[1])
q[2] = tonumber(s[2]..t[2])
return q
end
function p.concatene()
local S = {}
S = B..C
return "En concatènant les deux tables B et C, nous avons obtenu : ("..S[1]..","..S[2]..")."
end
return p
return p
En tapant {{#invoke:Champ|concatene}}, on obtient : En concatènant les deux tables B et C, avons obtenu : (31,79).
Le champ __pow
[modifier | modifier le wikicode]Ce champ nous permet d'accéder à une méta-méthode permettant d'élever une table à une certaine puissance. Pour simplifier, l'exemple ci-dessous se contente d'élever chaque nombre de la table à la puissance indiquée. On pourrait, pour compliquer, imaginer une méta-méthode qui élève une matrice carrée à une certaine puissance entière.
local p = {}
local B = {3,7}
local Point = {}
setmetatable(B,Point)
function Point.__pow(s,e)
local q = {}
q[1] = s[1]^e
q[2] = s[2]^e
return q
end
function p.puissance()
local S = {}
S = B^2
return "En élevant la table B à la puissance 2, nous obtenons : ("..S[1]..","..S[2]..")."
end
return p
return p
En tapant {{#invoke:Champ|puissance}}, on obtient : En élevant la table B à la puissance 2, nous obtenons : (9,49).
Le champ __mod
[modifier | modifier le wikicode]La fonction modulo donne le reste de la division par un nombre donné. L'exemple ci-dessous nous donne une table contenant les restes des divisions des nombres de la table donnée par un nombre donnée.
local p = {}
local B = {3,7}
local Point = {}
setmetatable(B,Point)
function Point.__mod(s,m)
local q = {}
q[1] = s[1]%m
q[2] = s[2]%m
return q
end
function p.modulo()
local S = {}
S = B%3
return "En calculant la classe modulo 3 de la table B, nous obtenons : ("..S[1]..","..S[2]..")."
end
return p
return p
En tapant {{#invoke:Champ|modulo}}, on obtient : En calculant la classe modulo 3 de la table B, nous obtenons : (0,1).
Le champ __unm
[modifier | modifier le wikicode]Ce champ nous fournit une méta-méthode pour interpréter ce que peut donner une table affectée du signe -. Dans notre exemple, on obtiendra une table ou les signes respectifs de toutes ses valeurs sont inversés.
local p = {}
local B = {3,7}
local Point = {}
setmetatable(B,Point)
function Point.__unm(s,m)
local q = {}
q[1] = -s[1]
q[2] = -s[2]
return q
end
function p.negation()
local S = {}
S = -B
return "En calculant l'opposée de la table B, nous obtenons : ("..S[1]..","..S[2]..")."
end
return p
En tapant {{#invoke:Champ|negation}}, on obtient : En calculant l'opposée de la table B, nous obtenons : (-3,-7).
Le champ __eq
[modifier | modifier le wikicode]La comparaisons a == b est valable si a et b sont des tables. a == b retournera true si la table a contient les mêmes éléments que la table b. Mais on peut souhaiter un autre comportement.
Ce champ nous permet d'interpréter ce que peut donner l'opérateur relationnel == placé entre deux tables. Par exemple, comme c’est le cas dans notre exemple, on peut vouloir tester l'égalité approximative entre deux tables. Il y a une différence entre ce champ et les champs vus précédemment, c’est que les deux tables concernées doivent avoir la même méta-méthode dans leur méta-table. Le plus simple est en fait qu’elles aient la même méta-table. Il en sera de même pour les autres opérateurs relationnels <, >, <=, >=.
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__eq(s,t)
if math.abs(s[1] - t[1]) < 0.1 and math.abs(s[2] - t[2]) < 0.1 then
return true
else
return false
end
end
function p.egal()
if B == C then
return "Les tables B et C sont égales."
else
return "Les tables B et C sont différentes."
end
end
return p
En tapant {{#invoke:Champ|egal}}, on obtient : Les tables B et C sont différentes.
Comme pour le champs __index, on peut souhaiter que la méta-méthode soit ignorée dans certain cas. On utilisera alors la fonction rawequal utilisant deux paramètres sous la forme rawequal(a,b) qui est équivalent à a == b mais qui ignore la méta-méthode de ce paragraphe.
Le champ __le
[modifier | modifier le wikicode]Ce champ nous permet d'interpréter ce que peuvent donner les opérateurs relationnels >= et <= placés entre deux tables. En principe, comme c’est le cas dans notre exemple, il s'agit de tester si une table est inférieure ou égale ou supérieure ou égale à l'autre. Toutefois, comme il y a plusieurs nombres dans la table, on peut avoir le choix sur la méta-méthode. Dans notre exemple, on dira qu'une table est supérieure ou égale à une autre table si la somme des nombres appartenant à la première table est supérieure ou égale à la somme des nombres appartenant à la deuxième table. Il y a une différence entre ce champ et les champs non relationnels, c’est que les deux tables concernées doivent avoir la même méta-méthode dans leur méta-table. Le plus simple est en fait qu’elles aient la même méta-table. Il en sera de même pour les autres opérateurs relationnels <, >, ==.
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__le(s,t)
if s[1] + s[2] >= t[1] + t[2] then
return true
else
return false
end
end
function p.superieur()
if B >= C then
return "La table B est supérieure ou égale à la table C."
else
return "La table B est strictement infèrieure à la table C."
end
end
return p
En tapant {{#invoke:Champ|superieur}}, on obtient : La table B est supérieure ou égale à la table C.
Le champ __lt
[modifier | modifier le wikicode]Ce champ nous permet d'interpréter ce que peuvent donner les opérateurs relationnels > et < placés entre deux tables. En principe, comme c’est le cas dans notre exemple, il s'agit de tester si une table est inférieure ou supérieure à l'autre. Toutefois, comme il y a plusieurs nombres dans la table, on peut avoir le choix sur la méta-méthode. Dans notre exemple, on dira qu'une table est inférieure à une autre table si la somme des nombres appartenant à la première table est inférieure à la somme des nombres appartenant à la deuxième table. Il y a une différence entre ce champ et les champs non relationnels, c’est que les deux tables concernées doivent avoir la même méta-méthode dans leur méta-table. Le plus simple est en fait qu’elles aient la même méta-table. Il en sera de même pour les autres opérateurs relationnels <=, >=, ==.
local p = {}
local B = {3,7}
local C = {1,9}
local Point = {}
setmetatable(B,Point)
setmetatable(C,Point)
function Point.__lt(s,t)
if s[1] + s[2] < t[1] + t[2] then
return true
else
return false
end
end
function p.inferieur()
if B < C then
return "La table B est strictement infèrieure à la table C."
else
return "La table B est supérieure ou égale à la table C."
end
end
return p
En tapant {{#invoke:Champ|inferieur}}, on obtient : La table B est supérieure ou égale à la table C.
Le champ __ipairs
[modifier | modifier le wikicode]Ce champ permet de donner une alternative à la fonction ipairs. Si ce champ existe, l'appel à la fonction ipairs nous retournera la fonction itérative, la table, et l'instruction d'arrêt prévu par la méta-méthode __ipairs.
Dans l'exemple qui suit, la méta-méthode associée à la fonction ipairs est programmée de façon à sélectionner un objet sur deux dans la table souk. L'appel à la fonction ipairs, dans la fonction p.liste, nous retournera donc une fonction itérative suivant qui incrémente la clé de deux unités au lieu d'une.
local p = {}
local souk = {"flute", "pipo", "manche à balaie", "serpière", "jeu de cartes", "coton tige", "tourne vis", "rateau", "stylo", "poupée"}
local entrepot = {}
setmetatable(souk,entrepot)
function suivant(tab,n)
if n == nil then n = -1 end
if tab[n+2] == nil then
return nil,nil
else
return n+2,tab[n+2]
end
end
function entrepot.__ipairs(t)
return suivant,t,nil
end
function p.liste()
local reponse = " "
for index, objet in ipairs(souk) do
reponse = reponse.."<br>à la clé numéro "..index.." se trouve l’objet "..objet.."."
end
return reponse
end
return p
En tapant {{#invoke:Champ|liste}}, on obtient :
à la clé numéro 1 se trouve l’objet flute.
à la clé numéro 3 se trouve l’objet manche à balaie.
à la clé numéro 5 se trouve l’objet jeu de cartes.
à la clé numéro 7 se trouve l’objet tourne vis.
à la clé numéro 9 se trouve l’objet stylo.
Le champ __metatable
[modifier | modifier le wikicode]La grande nouveauté pour ce champ est qu’il n'indexe pas une fonction. Ce champ indexe une valeur qui peut être une chaîne de caractères, un nombre, un booléen ou une table. Dans notre exemple, ce champ indexe la chaîne de caractère "Coordonnées" pour indiquer que la méta-table est associée à des tables qui contiennent des coordonnées. Lorsqu'on utilise la fonction getmetatable sur une table ayant pour méta-table, une table contenant ce champ, au lieu d'obtenir la méta-table de la table, on obtiendra le message "Coordonnées".
local p = {}
local B = {3,7}
local Point = {}
setmetatable(B,Point)
Point.__metatable = "Coordonnées"
function p.obtenir()
return getmetatable(B)
end
return p
En tapant {{#invoke:Champ|obtenir}}, on obtient : Coordonnées
Le champ __tostring
[modifier | modifier le wikicode]Ce champ indexe une méta-méthode indiquant ce que doit donner la fonction tostring si on l'applique à une table (qui a une méta-table contenant la méta-méthode en question). Dans notre exemple, on obtiendra une chaîne de caractères représentant la table formée des deux coordonnées séparées par une virgule et se trouvant entre parenthèses.
local p = {}
local B = {3,7}
local Point = {}
setmetatable(B,Point)
function Point.__tostring(q)
return "("..q[1]..","..q[2]..")"
end
function p.convertir()
return tostring(B)
end
return p
En tapant {{#invoke:Champ|convertir}}, on obtient : (3,7)
Le champ __call
[modifier | modifier le wikicode]Ce champ permet de transformer une table en fonction lorsqu'on l'emploie en utilisant la syntaxe d'une fonction. Dans l'exemple ci-dessous, la table tb devient une fonction tb qui ajoute 3 à son argument. tb(17) donne alors 20.
local p = {}
local newtable = {}
function newtable.__call(tab,x)
return x + 3
end
function p.appel()
local tb = {}
setmetatable(tb,newtable)
return tb(17)
end
return p
En tapant {{#invoke:Champ|appel}}, on obtient : 20
Le champ __newindex
[modifier | modifier le wikicode]Ce champ permet de rendre possible une assignation dans une table, pour une clé donnée, uniquement s'il y a déjà une valeur pour cette clé. Autrement dit, on peut modifier des valeurs déjà existantes dans la table mais on ne peut pas en ajouter de nouvelles. Dans l'exemple ci-dessous, nous avons prévu deux fonctions p.assigne1 et p.assigne2. Dans la fonction p.assigne1, nous voyons que nous avons déclaré une table tb sans l'initialiser. L'instruction tb[2] = "Framboise" est donc sans effet. Nous le constatons en retournant le type de tb[2] qui est nil indiquant que l'assignation a été inopérante. Dans la fonction p.assigne2, nous voyons que nous avons déclaré une table tb en l'initialisant. L'instruction tb[2] = "Framboise" est donc opérationnelle. Nous pouvons le constater en retournant la valeur de tb[2] qui est Framboise indiquant que l'assignation a été faîte.
local p = {}
local newtable = {}
function newtable.__newindex(tab,cle,valeur)
return "Bienvenue dans la table"
end
function p.assignation1()
local tb = {}
local reponse
setmetatable(tb,newtable)
tb[2] = "Framboise"
return type(tb[2])
end
function p.assignation2()
local tb = {"Moustique","Montagne","boulet"}
local reponse
setmetatable(tb,newtable)
tb[2] = "Framboise"
return tb[2]
end
return p
En tapant {{#invoke:Champ|assignation1}}, on obtient : nil
En tapant {{#invoke:Champ|assignation2}}, on obtient : Framboise
Comme pour le champs __index, on peut souhaiter que la méta-méthode soit ignorée dans certain cas. On utilisera alors la fonction rawset utilisant trois paramètres sous la forme rawset( tab, k, v ) qui est équivalente à tab[k] = v mais qui ignore la méta-méthode de ce paragraphe.
Le champ __mode
[modifier | modifier le wikicode]Ce champ permet de mettre des références faibles dans une table. Si ce champ est utilisé, l'espace alloué à une valeur de la table peut être récupéré par le ramasse miette. Les valeurs stockées dans la table peuvent ainsi être perdues aléatoirement. Si le champ indexe la chaîne "k", les clés de la table peuvent être nettoyées. Si le champ indexe la chaîne "v", les valeurs de la table peuvent être nettoyées.
Il est difficile de donner un exemple pour ce champ. Nous en resterons donc là !
local p = {}
return p
En tapant {{#invoke:Champ|faible}}, on obtient : table
Librairies Scribunto
Les librairies Scribunto apporte un ensembles de fonctions spécialisées plus particulièrement adaptés aux projets de la fondation Wikimédia.
Présentation
[modifier | modifier le wikicode]Les librairies Scribunto sont constituées d'ensembles d'objets, chacun des ensembles étant spécialisé dans un domaine particulier. Par exemple, la librairie frame est spécialisée dans l'interface entre l'utilisateur et le programme. La librairie Title sera spécialisée dans l'accès aux pages d'un Wiki, etc.
Les librairies Scribunto sont des ajouts qui ne font pas partie du Lua standard. Elles ont été écrites spécialement pour les projets Wikimédia. Elles permettent de faciliter l'écriture de programmes en Lua concernant la gestion des projets.
Toutes ces librairies sont constituées d'une table contenant principalement des fonctions mais pouvant aussi contenir d'autres objets comme des chaînes de caractères, des sous-tables, des booléens etc.
Toutes les librairies Scribunto se trouvent dans une table principale qui se nomme mw. Pour accéder à une librairie, il suffira donc de taper mw."nom de la librairie". Supposons que l’on veuille accéder à la fonction indexée par new se trouvant dans la librairie message, il nous suffira de taper mw.message.new(paramètres) (nous rappelons que new est en fait un index de la table mw.message, message étant lui même un index de la table mw)
Une particularité de la plupart des librairies Scribunto est de travailler sur une ou plusieurs tables fétiches de la librairie considérée. Ces tables porteront le nom d'objet suivi du nom de la librairie. par exemple la librairie Title travaillera sur des objets Title. Les tables constituant les objets fétiches d'une librairie contiennent, bien sûr, elles aussi, des fonctions ou autre chose (sous-tables, chaînes de caractères, booléens,etc.).
Supposons que, dans le cadre de la librairie message, on ait créé un objet message du nom de cocorico par exemple. Cet objet message va contenir tout un ensemble de fonctions parmi lesquelles se trouve la fonction indexée par plain. Il y a deux façons d'accéder à cette fonction. On a tout d’abord la façon habituelle, c'est-à-dire cocorico.plain(paramètres). Mais il existe une autre façon qui consiste à écrire mw.message:plain(paramètres). On remarque la présence de : dans mw.message:plain qui indique que l’on va chercher la fonction, non pas dans la table mw.message, mais dans un objet message dont le nom figure parmi les paramètres (ou pas, s'il n'y a pas de confusion possible sur l’objet visé comme l'objet frame de la librairie frame)
Nous venons de voir qu’il existe deux façons d'accéder à une fonction se trouvant dans un objet fétiche d'une librairie. Si l’on reprend l'exemple précédent, on a : cocorico.plain(paramètres) ou l’on a : mw.message:plain(paramètres). Par convention, pour différencier ces deux façons, nous dirons que la notation cocorico.plain désigne une fonction alors que la notation mw.message:plain désigne une méthode (même si une méthode n'est rien d’autre qu'une fonction). Comme, le plus souvent, on accède aux fonctions se trouvant dans les objets fétiches avec la notation méthode, on dira que les fonctions se trouvant dans les objets fétiches sont des méthodes. Si, par exemple, nous disons que dans la librairie langage, il y a 9 fonctions et 20 méthodes, nous voulons dire par là que 9 fonctions sont indexées directement dans la table mw.langage et 20 fonctions sont indexées dans un objet langage qui sera créé dans le cadre de cette librairie.
Ce que nous venons de dire est très théorique et comporte des exceptions. Nous verrons, par exemple, qu’il n'existe pas de librairie frame mais qu’il existe malgré tout un objet frame. Nous verrons aussi que les méthodes, dans la librairie HTML, n'apparaissent pas dans un objet html.
Fonctions et tables constituant les librairies Scribunto
[modifier | modifier le wikicode]Toutes les librairies Scribunto se trouvent dans la table mw
. Nous commencerons donc par visualiser le contenu de cette table grâce au programme suivant :
local p = {}
function p.visualisation(frame)
reponse = ""
for index, objet in pairs(mw) do
reponse = reponse.."<br />À la clé "..index..", on trouve un objet de type : "..type(objet)
end
return reponse
end
return p
{{#invoke:Scribunto|visualisation}} nous donne :
À la clé incrementExpensiveFunctionCount, on trouve un objet de type : function
À la clé log, on trouve un objet de type : function
À la clé addWarning, on trouve un objet de type : function
À la clé getLanguage, on trouve un objet de type : function
À la clé getCurrentFrame, on trouve un objet de type : function
À la clé loadJsonData, on trouve un objet de type : function
À la clé logObject, on trouve un objet de type : function
À la clé allToString, on trouve un objet de type : function
À la clé executeFunction, on trouve un objet de type : function
À la clé loadData, on trouve un objet de type : function
À la clé hash, on trouve un objet de type : table
À la clé title, on trouve un objet de type : table
À la clé uri, on trouve un objet de type : table
À la clé clone, on trouve un objet de type : function
À la clé text, on trouve un objet de type : table
À la clé language, on trouve un objet de type : table
À la clé message, on trouve un objet de type : table
À la clé wikibase, on trouve un objet de type : table
À la clé site, on trouve un objet de type : table
À la clé ustring, on trouve un objet de type : table
À la clé isSubsting, on trouve un objet de type : function
À la clé dumpObject, on trouve un objet de type : function
À la clé ext, on trouve un objet de type : table
À la clé html, on trouve un objet de type : table
À la clé getContentLanguage, on trouve un objet de type : function
Nous voyons que nous avons 9 tables constituant 9 librairies spécialisées, mais nous voyons aussi apparaître 12 fonctions. Nous allons étudier les 12 fonctions que nous désignerons sous le terme "fonctions de base" dans le paragraphe suivant. Nous présenterons ensuite les 9 tables constituant les 9 librairies spécialisées qui contiennent chacune des fonctions spécialisées. Dans ce chapitre, nous n'étudierons pas les 9 librairies spécialisées pour ne pas surcharger de façon colossale ce chapitre. Cette étude sera effectuée dans d'autres chapitres.
Fonctions de base
[modifier | modifier le wikicode]mw.allToString
[modifier | modifier le wikicode]mw.allToString( ... )
Appelle tostring() sur tous ses paramètres, puis concatène les résultats avec une tabulation comme séparateur.
mw.clone
[modifier | modifier le wikicode]mw.clone( value )
Crée une copie de la valeur. Toutes les tables (et leurs méta-tables) sont reconstruites de zéro. Les fonctions restent partagées quoi qu’il en soit.
mw:executeFunction
[modifier | modifier le wikicode]mw.getCurrentFrame
[modifier | modifier le wikicode]mw.getCurrentFrame()
Retourne l'objet frame courant.
mw:getContentLanguage
[modifier | modifier le wikicode]mw:getLanguage
[modifier | modifier le wikicode]
mw.incrementExpensiveFunctionCount
[modifier | modifier le wikicode]mw.incrementExpensiveFunctionCount()
Ajoute un au compteur des « parser-function coûteuses », et génère une exception si ce compteur dépasse la limite (voir $wgExpensiveParserFunctionLimit).
mw.isSubsting
[modifier | modifier le wikicode]mw.isSubsting()
Renvoie true si le #invoke
courant est en train d’être « substé », false dans le cas contraire. Voir Retourner du texte plus haut pour connaitre la différence entre un #invoke
substé ou non.
mw.loadData
[modifier | modifier le wikicode]mw.loadData( module )
Parfois un module a besoin de grandes tables de données. Par exemple, un module générique de conversion d'unités devrait avoir une grande table contenant toutes les unités connues et leurs facteurs de conversion. Et parfois ces modules vont être utilisés de nombreuses fois dans une même page. Interpréter de grandes tables de données pour chaque {{#invoke:}}
peut prendre un temps non négligeable. Pour éviter ce problème, la fonction mw.loadData()
existe.
mw.loadData
fonctionne comme require()
avec les différences suivantes :
- Le module n'est évalué qu'une fois par page, plutôt qu'une fois par appel à
{{#invoke:}}
. - Le module n’est pas enregistré dans
package.loaded
. - La valeur retournée par le module chargé doit être une table. Les autres types ne sont pas gérés.
- La table retournée (et ses sous-tables) peut contenir des booléens, des nombres, des chaînes et d'autres tables. Les autres types, en particulier les fonctions, ne sont pas autorisés.
- La table retournée (et toutes ses sous-tables) ne doit pas avoir de méta-tables.
- Toutes les clés de la table doivent être des booléens, des nombres ou des chaînes.
- La table réellement retournée par
mw.loadData()
a des méta-méthodes qui fournissent un accès en lecture seul à cette table. Dans la mesure où elle ne contient pas directement les données,pairs()
etipairs()
fonctionnent mais les autres méthodes, y comprisInitiation au Lua avec Scribunto/Fonctions basiques#value
,next()
, et les fonctions de la librairie Table, ne fonctionneront pas correctement.
Dans l'hypothétique module évoqué plus haut, le module pourrait être « Module:Convert » et les données pourraient être stockées dans « Module:Convert/data ». Le module utiliserait local data = mw.loadData( 'Module:Convert/data' )
pour charger efficacement les données.
mw.dumpObject
[modifier | modifier le wikicode]mw.dumpObject( object )
Renvoie une représentation lisible par un humain de l’objet object
, sous forme de chaîne.
mw.log
[modifier | modifier le wikicode]mw.log( ... )
Envoie ses paramètres à mw.allToString() puis ajoute le résultat au tampon des enregistrements (log).
Dans la console de debug, la fonction print()
est un alias de cette fonction.
mw.logObject
[modifier | modifier le wikicode]mw.logObject( object )
mw.logObject( object, prefix )
Appelle mw.dumpObject() et concatène la chaîne résultante au « log buffer » (traduction ?). Si prefix
est renseigné, il sera ajouté au « log buffer » suivi d'un signe égal avant que la chaîne produite soit concaténée (i.e. le texte loggé sera "prefix = object-string").
Les tables constituant les librairies Scribunto
[modifier | modifier le wikicode]La table mw.html
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie HTML. Pour éviter de surcharger ce chapitre, l'étude de la librairie HTML sera faite dans le chapitre 15.
La table mw.language
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Language. Pour éviter de surcharger ce chapitre, l'étude de la librairie langage sera faite dans le chapitre 16.
La table mw.text
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Text. La librairie text fournit quelques fonctions utiles de traitement de texte absentes de la librairie String et de la librairie Ustring. Ces fonctions peuvent fonctionner avec les caractères UTF-8.
Les fonctions de cette librairie ont déjà été étudiées dans le chapitre 6 pour deux raisons : d'une part, pour alléger le présent chapitre qui est déjà volumineux et ensuite parce que ces fonctions agissent sur les chaînes de caractères, ce qui est l’objet du chapitre 6.
La table mw.ustring
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Ustring. Les fonctions de cette librairie ont déjà été étudiées dans le chapitre 6 pour deux raisons : d'une part, pour alléger le présent chapitre qui est déjà volumineux et ensuite parce que ces fonctions agissent sur les chaînes de caractères, ce qui est l’objet du chapitre 6.
La table mw.message
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Message. Pour éviter de surcharger ce chapitre, l'étude de la librairie message sera faite dans le chapitre 17
La table mw.site
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Site. Pour éviter de surcharger ce chapitre, l'étude de la librairie site sera faite dans le chapitre 18
La table mw.title
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Title. Pour éviter de surcharger ce chapitre, l'étude de l’objet Title sera faite dans le chapitre 13
La table mw.ext
[modifier | modifier le wikicode]Cette table semble n'avoir rien à voir avec notre propos. Si l’on regarde ce qu'elle contient, on y trouve les deux sous-tables :
- mw.ext.ParserFunctions
- mw.ext.TitleBlacklist
La table mw.uri
[modifier | modifier le wikicode]Cette table héberge les éléments de la librairie Uri. Pour éviter de surcharger ce chapitre, l'étude de l’objet URI sera faite dans le chapitre 14
L'objet Frame
[modifier | modifier le wikicode]On aurait pu s'attendre à voir apparaître une table mw.frame dans la table mw. Mais cela n’est pas arrivé. On peut admettre l’existence d'une table virtuelle mw.frame puisqu’il existe un objet frame. L'objet frame est l'interface des paramètres passés au {{#invoke:}}
, ainsi que l'interface au parseur.
Pour éviter de surcharger ce chapitre, l'étude de l’objet Frame sera faite dans le chapitre 12
L'objet Frame
Ce paragraphe étudie plus en détail l’objet frame que l’on peut considérer comme étant l'interface avec le programme. C'est l’objet frame qui reçoit les paramètres que l’on envoie au programme grâce à la commande #invoke.
Nature de l’objet frame
[modifier | modifier le wikicode]Une remarque que l’on peut faire à propos de l’objet frame et de la façon de l’utiliser comme l'accès aux arguments frame.args[1], frame.args[2], etc. est la ressemblance avec les notations relatives aux tables. frame serait une table et frame.args que l’on pourrait aussi noter frame["args"] serait une table, indexée par la chaîne de caractères "args", se trouvant dans la table frame. Par contre frame.args serait une table à accès numérique. C'est pour cela que l’on note ses arguments frame.args[1], frame.args[2], etc.
En réalité, ce que l’on vient de dire n’est pas tout à fait vrai. À l'appel d'un module avec #invoke, la table frame n’est pas créée et remplie tel que l’on vient de le dire. Quand l’on fait appel à un élément de la table numérique frame.arg, une méta-méthode est activée pour demander l’objet souhaité au logiciel MediaWiki. La table frame est en réalité une méta-table remplie de méta-méthodes dont la fonction est de simuler l’existence d'une table qui contiendrait les éléments décrits plus haut alors, qu'en réalité, ils sont demandés au fur et à mesure des besoins au logiciel MediaWiki.
Comme "frame" n’est pas une vraie table, on n’est pas assuré que toutes les fonctions agissant sur les tables vont fonctionner correctement. Nous devons donc connaître la liste des fonctions qui fonctionnent correctement et la liste des fonctions qui ne fonctionnent pas correctement, ces deux listes étant susceptibles d’être modifiées au fil des versions de Lua et de Scribunto.
Les fonctions sur lesquelles, on peut compter actuellement sont ipairs et pairs.
Nous allons donc donner un exemple simple utilisant la fonction ipair.
Dans le Module:Frame, écrivons une fonction p.evalue qui compte combien il y a d'arguments contenant une valeur numérique et combien il y a d'arguments contenant une chaîne de caractères.
local p = {}
function p.evalue(frame)
local reponse = " "
local nombre,chaine = 0,0
for index, objet in ipairs(frame.args) do
if tonumber(objet) == nil then
chaine = chaine + 1
else
nombre = nombre + 1
end
end
reponse = reponse.."Il y a "..nombre.." nombres et "..chaine.." chaines de caractères."
return reponse
end
return p
{{#invoke:Frame|evalue|Laura|54|76|Mouchoir|17|Schtroumpfs|11|2}}, nous donne : Il y a 5 nombres et 3 chaines de caractères.
Arguments avec clé sous forme de chaîne de caratères
[modifier | modifier le wikicode]Jusqu'à maintenant, nous avons déclaré, dans la commande #invoke, les arguments à la suite les uns des autres comme on déclare les objets d'une table à clé numérique. Le premier argument s'associant automatiquement à la clé 1, le deuxième à la clé 2 et ainsi de suite. Toutefois, de même que l’on a vu que, dans les tables, il est possible de créer des clés sous forme de chaîne de caractères, nous allons voir qu’il en est de même pour la pseudo-table frame.args. Pour cela, il suffit de déclarer, dans la commande #invoke, les arguments avec clé sous forme de chaîne de caractères comme on le ferait pour une table.
Par exemple, on écrira : {{#invoke:''Module''|''fonction''|fleur=rose}}
Il est à noter que la déclaration : {{#invoke:''Module''|''fonction''|["fleur"]=rose}} n’est pas correcte car ["fleur"] sera interprété comme la chaîne de caractère "["fleur"]" (voir l'exemple ci-dessous avec ("personnage"]).
Écrivons un exemple pour voir si cela marche bien !
Dans le Module:Frame, écrivons une fonction p.renvoie qui nous renvoie les clés utilisés avec les objets correspondants.
local p = {}
function p.renvoie(frame)
local reponse = " "
for index, objet in pairs(frame.args) do
reponse = reponse.."<br>À la clé "..index..", il y a l’objet : "..objet
end
return reponse
end
return p
{{#invoke:Frame|renvoie|prénom=Laura|nombre=54|76|Mouchoir|17|["personnage"]=Schtroumpfs|nombre premier=11|2}}, nous donne :
À la clé 1, il y a l’objet : 76
À la clé 2, il y a l’objet : Mouchoir
À la clé 3, il y a l’objet : 17
À la clé 4, il y a l’objet : 2
À la clé prénom, il y a l’objet : Laura
À la clé nombre, il y a l’objet : 54
À la clé nombre premier, il y a l’objet : 11
À la clé ["personnage"], il y a l’objet : Schtroumpfs
Dans le programme précédent, nous avons utilisé la fonction pairs. Si nous avions utilisé la fonction ipairs, tous les objets dont la clé est une chaîne de caractères auraient été ignorés.
Fonctions en relation avec l’objet frame
[modifier | modifier le wikicode]Tous les exemples de ce paragraphe se trouvent dans le Module:Frame.
Les fonctions que nous allons étudier dans ce paragraphe se trouvent dans l’objet frame. Nous commencerons donc par écrire une fonction p.visualisation permettant de les visualiser.
(Ici, contrairement aux paragraphes précédents, nous visualisons ce qui se trouve dans frame et pas ce qui se trouve dans frame.args.)
local p = {}
function p.visualise(frame)
reponse = ""
for index, objet in pairs(frame) do
reponse = reponse.."<br />À la clé "..index..", on trouve un objet de type : "..type(objet)
end
return reponse
end
return p
{{#invoke:Frame|visualise}} nous donne :
À la clé newTemplateParserValue, on trouve un objet de type : function
À la clé getParent, on trouve un objet de type : function
À la clé argumentPairs, on trouve un objet de type : function
À la clé extensionTag, on trouve un objet de type : function
À la clé callParserFunction, on trouve un objet de type : function
À la clé preprocess, on trouve un objet de type : function
À la clé getTitle, on trouve un objet de type : function
À la clé args, on trouve un objet de type : table
À la clé newParserValue, on trouve un objet de type : function
À la clé expandTemplate, on trouve un objet de type : function
À la clé getArgument, on trouve un objet de type : function
À la clé newChild, on trouve un objet de type : function
En dehors de la clé args qui référence la table contenant les arguments transmis par la commande #invoke, nous voyons que nous disposons de 11 fonctions que nous allons étudier en détail ci-dessous.
Une petite remarque que l’on peut faire ici est que la notation des fonctions diffère légèrement de ce que l’on avait mentionné auparavant. En effet, au lieu de noter, par exemple, la deuxième fonction de la liste frame.getParent (avec .), nous la noterons frame:getParent (avec :). Cette différence de notation tient au type de propriété à laquelle on accède. Le point permet d'accéder à la propriété définie dans l'objet frame tandis que le deux-points permet d'exécuter une fonction définie sur l'objet.
Autre remarque, les fonctions des librairies sont accessibles comme des propriétés. C'est pourquoi on utilise le point pour accéder à une fonction d'une librairie et non le deux-points car la librairie n’est pas instanciée (n'est pas un objet) : string.len(s). Quand on a à faire à un objet, l'appel à la méthode se fait par les deux-points : s:len().
Comme d'habitude, les fonctions ne seront pas étudiées dans l’ordre !
frame:getParent
[modifier | modifier le wikicode]Cette fonction permet de connaître, à l'intérieur du module, les arguments du modèle qui a appelé le module où se trouve la fonction frame:getParent.
La fonction frame:getParent retourne une table contenant une table, indexée par la chaîne de caractère "args", qui contient tous les paramètres passés au modèle. Par exemple, si la fonction frame:getParent se trouve dans un module placé dans un modèle dont l'appel se fait ainsi :
{{Essai
| titre = stat
| compte = ouvert
}}
Et si dans le module, nous avons l'instruction :
local model = frame:getParent()
Alors model sera une table contenant la table model.args. La table model.args aura deux éléments : l'élément "stat" indexé par la chaîne de caractère "titre" et l'élément "ouvert" indexé par la chaîne de caractère "compte"
Si le modèle appelant possède un lien qui n’est pas renseigné, alors La table model.args retournera, à l'index correspondant, la chaîne vide "" (et pas nil).
À titre d'exemple, dans le Module:Frame nous écrivons la fonction p.Parent ainsi :
local p = {}
function p.parent(frame)
local reponse = ""
local model = frame:getParent()
for index, objet in pairs(model.args) do
reponse = reponse.."<br>À la clé "..index..", il y a l’objet : "..objet
end
return reponse
end
return p
Nous créons ensuite un Modèle:Parents dans lequel nous écrivons :
Les arguments du présent modèle sont :{{#invoke:Frame|parent}}
Nous donnerons deux exemples.
Premier exemple :
{{Parents|Grenouille|rouge|4}}
nous donne :
Les arguments du présent modèle sont :
À la clé 1, il y a l’objet : Grenouille
À la clé 2, il y a l’objet : rouge
À la clé 3, il y a l’objet : 4
Deuxième exemple :
{{Parents
| titre = Planing
| couleur = vert
| nombre = 63
}}
nous donne :
Les arguments du présent modèle sont :
À la clé nombre, il y a l’objet : 63
À la clé titre, il y a l’objet : Planing
À la clé couleur, il y a l’objet : vert
frame:newChild
[modifier | modifier le wikicode]Cette fonction permet d’utiliser des fonctions qui, normalement, attendent des arguments provenant d'une commande #invoke.
Dans l'exemple ci-dessous la fonction p.replique est rédigée comme si elle devait être utilisée grâce à l'appel {{#invoke:Frame|replique|jardin|maison}} par exemple. La fonction p.child fabrique un nouvel objet frame avec pour arguments "jeudi" et frame.args[2] et appelle la fonction p.replique sous la forme p.replique(newFrame), ce qui est alors équivalent à un appel du type {{#invoke:Frame|replique|jeudi|frame.args[2]}} que l’on n'aurait pas pu faire puisque nous ne pouvons pas utiliser la commande #invoke dans un module.
local p = {}
function p.replique(frame)
return "Je renvoie "..frame.args[1].." et "..frame.args[2]
end
function p.child(frame)
newFrame = frame:newChild{args={"Jeudi",frame.args[2]}}
return p.replique(newFrame)
end
return p
{{#invoke:Frame|child|3|Tulipe|7|janvier}} nous indique : Je renvoie Jeudi et Tulipe
frame:preprocess
[modifier | modifier le wikicode]Nous avons déjà eu l’occasion d'étudier cette fonction qui permet d'interpréter les modèles avant qu’ils ne soient retournés (voir le chapitre sur la Gestion de l'environnement).
Dans l'exemple ci-dessous, nous n'avons pas retourné directement ce que donnait la fonction frame:preprocess, mais nous avons commencé par stocker le retour dans la variable reponse et nous avons ensuite retourné la variable reponse. Ceci montre que ce que retourne la fonction frame:preprocess peut éventuellement être mémorisé dans une variable et subir un éventuel traitement dans le programme.
local p = {}
function p.process()
local reponse = ""
reponse = frame:preprocess("{{Attention|Essai avec le modèle Attention}}")
return "<br>La fonction a retourné : "..reponse
end
return p
{{#invoke:Frame|process}} nous indique :
La fonction a retourné :
Essai avec le modèle Attention |