# Initiation au Lua avec Scribunto/Version imprimable

* Si vous imprimez cette page, choisissez « Aperçu avant impression » dans votre navigateur, ou cliquez sur le lien Version imprimable dans la boîte à outils, vous verrez cette page sans ce message, ni éléments de navigation sur la gauche ou en haut.
* Cliquez sur Rafraîchir cette page pour obtenir la dernière version du cours.
* Pour plus d'informations sur les version imprimables, y compris la manière d'obtenir une version PDF, vous pouvez lire l’article Versions imprimables.
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
### L'espace module

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

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 :
Nous allons vous expliquer ce que signifie chacune des lignes du module.

#### La table p

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

Nous avons ensuite les lignes :
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 :
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

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
### Exemple avec un seul paramètre

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 :
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

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 :
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 :
Le bloc d'instructions comprenant les 7 traductions en anglais sous forme de 7 structures if condition then instruction end.

## Concaténer du texte

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 :
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

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 :
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 :
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 :
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 :
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 :
affecte à la variable poids la chaîne de caractères "56", et l'instruction :
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 :
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 :
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 :
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 :
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 :
Ça marche ! Mais :
ne fait pas professionnel et ressemble à du bricolage. Nous abandonnerons donc cette idée au profil de :

## La structure if..then..else

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 :
Dans une autre page, si nous écrivons {{#invoke:Balance|alerte3|57}}, nous obtenons : « Attention, vous commencez à grossir ! »

## Traiter plusieurs variables en une seule instruction

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 :
c'est-à-dire mettre la valeur 2 dans a, puis la valeur 7 dans b, on peut écrire :
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 :
Dans un autre langage qui n'a pas l'affectation simultanée, on aurait été tenté d'écrire :
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 :
ou déclarer simultanément deux variables sans les initialiser :
| Sommaire | Mise au point d'un module |

# 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

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

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 :
Le programme aurait, tout de même, bien fonctionné mais aurait été moins lisible.

### Les noms de variable explicites

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

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 :
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 :

## L'éditeur Scribunto

* 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

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

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

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

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 :
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é :
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 :
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 :
Elle nous répond 5.
Faisons maintenant une petite expérience et rajoutons la ligne : mw.log("Il fait beau !") dans notre programme ainsi :
Dans la console de débogage tapons à nouveau :
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 :
puis « Entrée ». Le message tapé remonte au dessus de la zone grisée. Nous tapons ensuite :
puis « Entrée ». Le second message tapé remonte au dessus de la zone grisée. Nous tapons alors :
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 :
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é :
Dans la console de débogage, nous taperons successivement les trois commandes :
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).
| Premières notions | Tables et fonctions |

# 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

Au premier chapitre, nous avons effleuré la définition d'une table lorsque nous avons étudié l'instruction
Mais une table ne s’appelle pas forcément p. On peut lui donner un nom plus parlant sur sa fonction. Par exemple :
Et il est possible de l'initialiser :
On aurait pu, tout aussi bien, créer une table de nombres. Par exemple :
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 :
En écrivant : {{#invoke:Traduit|baratin1|5}}, nous obtenons : La traduction de vendredi, en anglais, est friday

## Index évolués

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 :
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 :
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 :
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

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 :
Mathématiquement, ce genre de table peut correspondre à des matrices.
Par exemple, la matrice :
$A={\begin{pmatrix}2&1&-4&6\\5&-3&-2&4\\1&3&-4&7\\-5&3&2&5\\\end{pmatrix}}$
se déclarera :
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 :
Et que l’on écrit dans le programme, par exemple :
On obtiendra une erreur de script.

## Fonctions relatives aux tables

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
#### Fonctions appelées par une fonction

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é.
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

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 :
Plus remarquable encore, une fonction peut retourner plusieurs valeurs en les séparant par des virgules :
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 :
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:
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 :
Et si nous ne sommes intéressé que par la première valeur ? Dans ce cas, nous écrirons :
Et si nous ne sommes intéressé que par la troisième valeur ? Dans ce cas, nous écrirons :
Et si nous ne sommes intéressé que par les deux premières valeurs ? Dans ce cas, nous écrirons :
Et ainsi de suite !!

#### Fonctions récursives

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.
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

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 :
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
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 :
Mais nous aurions pu écrire :
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 :
En écrivant : {{#invoke:Ajout|incremente|47|3}}, nous obtenons : 50

## Fonctions ayant une table pour argument

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 :
Si dans une autre fonction, on écrit
À 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é :
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

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.
| Mise au point d'un module | Structures de contrôle |

# 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

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 :
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 :
Qui présente l'avantage de fournir à l'utilisatrice une information plus complète.
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 :

## La structure while..do

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 :
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

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 :
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

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

La première forme de la boucle for se rédige sous la forme :
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 :
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 :
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 :
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

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 :
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 :
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 :
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 :
{{#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 :
Cette fois, nous n'avons pas gagné grand-chose !

## Opérateurs logiques

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 :
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 :
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 :
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 :
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 :
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 :
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

Soit a et b deux variables booléennes et soit l'affectation :
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

Soit a et b deux variables booléennes et soit l'affectation :
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

Soit a une variable booléenne et soit l'affectation :
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 :
Si nous écrivons : {{#invoke:Logique|essai3|6}}, nous obtenons : Le nombre est strictement compris entre 1 et 8
Par contre, si nous écrivons :
nous obtenons une erreur de script !

## Opérateurs externes

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 :
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.