Scripts Lua simples. Langage de programmation Lua. Entre Lua et non-Lua

Récemment, un de mes amis proches a passé un entretien pour un emploi dans une entreprise locale de développement de jeux. Je ne vais pas citer de noms ici, sauf pour dire que c'était une sorte de grande boutique de développement de jeux à Vancouver. Il n'a pas obtenu le poste, mais aujourd'hui, il ne s'agit pas de lui. Personnellement, je pense que l'une des raisons était qu'il n'était pas assez convivial avec le langage de script qu'ils utilisent.

Introduction

Je suis impliqué dans ce domaine parce que j'enseigne la programmation de jeux aux étudiants, mais c'est un sujet auquel je n'ai pas prêté suffisamment d'attention dans le passé. Nous couvrons Unreal Script dans le cadre du cours Utilisation de scripts existants. Mais nous n'avons pas réellement considéré le moteur de script comme faisant partie des utilitaires ou du moteur. Alors, armé d’un site Web, j’ai décidé de briser cette petite barrière. Le résultat est décrit dans ce document.

La seule chose est que je ne suis pas sûr de la taille de ce document. Je peux le diviser en plusieurs parties plus petites ou publier le tout sous la forme d'un long discours du début à la fin. Quoi qu'il en soit, je le découvrirai un peu plus tard, une fois que j'aurai mis mes notes dans un format plus significatif et cohérent.

Pourquoi et pourquoi pas ?

Tout d’abord, pourquoi utiliser un langage de script ? Une grande partie de la logique du jeu peut être écrite dans un langage de script à des fins diverses, plutôt que de devoir être programmée dans le cadre du moteur de jeu. Par exemple, charger ou initialiser un niveau. Après avoir chargé un niveau, vous souhaiterez peut-être faire la transition de la scène vers le plan de jeu ou afficher un texte d'aperçu. À l'aide d'un système de script, vous pouvez faire en sorte que certains objets du jeu effectuent certaines tâches. Pensez également à mettre en œuvre l’intelligence artificielle. Les personnages non-joueurs doivent savoir quoi faire. Programmer chaque PNJ « manuellement » dans le corps du moteur de jeu compliquera inutilement la tâche. Lorsque vous souhaitez modifier le comportement des PNJ, vous devrez recompiler votre projet. Avec un système de script, vous pouvez le faire de manière interactive, en modifiant le comportement et en enregistrant les paramètres.

J'ai abordé un peu cette question dans le dernier paragraphe, nous en reparlerons un peu plus tard. La question est : pourquoi ne pas écrire la logique exclusivement en C/C++ ? En termes simples, le point de vue du programmeur est que tout lui incombe directement et qu'il commencera en conséquence avec le code du jeu, en même temps il devra écrire le moteur et les utilitaires, etc. Mais nous pouvons désormais, avec un simple langage de script, déléguer certaines tâches fonctionnelles aux concepteurs de niveaux. Ils peuvent commencer à bricoler le niveau et à optimiser le gameplay. Voici un exemple :

Imaginons que Joe, notre pauvre programmeur, écrit lui-même l'intégralité du moteur, des outils et de la logique du jeu. Oui, Joe aura du mal, mais supposons qu'il s'en fiche. Nous avons également Brandon, un concepteur de jeux. Brandon est un gars plutôt intelligent avec de bonnes idées sur le jeu. Ainsi, notre codeur Joe rampe et implémente toute la logique du jeu en utilisant les outils qu'il a développés sur la base de la conception initiale de Brandon. Tout va bien au bureau. La première étape est terminée et Joe et Brandon s'assoient dans la salle de réunion et vérifient leur travail considérable. Brandon remarque plusieurs problèmes avec le gameplay qui ne se comporte pas comme prévu. Joe revient donc au code et apporte les modifications requises. Ce processus peut prendre une journée, du moins s’il ne s’agit pas d’un changement anodin. Puis un autre jour pour recompiler le projet. Afin de ne pas perdre une journée supplémentaire, la plupart des bureaux abandonnent le processus d'assemblage du jour au lendemain. Ainsi, comme nous le voyons, 24 heures s’écoulent avant que Brandon ne voie le changement qu’il a demandé.

Imaginons maintenant que notre personnage principal, Joe, décide que l'implémentation de la logique du jeu utilise le moteur de script à son avantage. Cela prendra du temps au début, mais il pense que cela sera bénéfique à long terme. Ainsi, il transfère certaines fonctionnalités du moteur de jeu vers le système de script de jeu. Il écrit également toute la logique du jeu dans le système de script mentionné précédemment. Ainsi, lorsqu'il rencontre Brandon et que le concepteur remarque quelque chose qui ne correspond pas à son idée, Joe ouvre rapidement la console, apporte quelques modifications au script, redémarre le jeu et voit déjà le nouveau comportement. Les modifications peuvent être apportées et affichées immédiatement, plutôt que de devoir attendre la recompilation. Et si Joe était particulièrement expressif, le système de script pourrait être utilisé pour des utilitaires et mis à la disposition des concepteurs de niveaux lors de la construction des niveaux. Si nous avançons dans cette voie, alors, avec un peu de formation, les concepteurs de niveaux pourraient définir eux-mêmes les événements du jeu, tels que les déclencheurs, les portes et autres événements du jeu, et profiter de la vie sans déranger le programmeur.

Il s'agit d'un exemple plutôt artificiel et peut-être un peu exagéré, mais j'espère qu'il montre la différence entre les approches. Ce que nous allons donc essayer de faire avec un modèle comme celui-ci, c'est d'évoluer vers des données plus automatisées. Donc, essentiellement, où nous allons :

  1. Le codeur s'intéresse à l'écriture du code du moteur/des outils, pas à la logique du jeu.
  2. Du temps a été passé à écrire le moteur/les outils du jeu.
  3. Les designers aiment jouer avec les choses. Les scripts leur donnent la liberté de concevoir des niveaux et des fonctionnalités. Cela leur donne également plus de flexibilité pour expérimenter des choses qu'ils demanderaient normalement à un programmeur de faire.
  4. Vous ne devez pas recompiler si vous souhaitez modifier les fonctionnalités du jeu. Changez simplement le script.
  5. Vous souhaitez rompre le lien entre le code machine et le code du jeu. Ils devraient être deux parties distinctes. De cette façon, il sera pratique d'utiliser le moteur pour les suites ultérieures (j'espère).

Ici, je vais faire quelques prédictions. D’ici 5 ans, les level designers devront faire plus que simplement construire des niveaux. Ils doivent être capables d'utiliser des scripts pour les scènes de jeu. Plusieurs entreprises avant-gardistes ont déjà adopté cette approche. Vous pouvez également voir cette méthode d'intégration dans des éditeurs comme UnrealEd et Aurora Toolset Bioware.

Clarification et divagation

J'espère que vous avez déjà cru à mes paroles et que vous souhaitez inclure un composant de script dans votre jeu. Alors, la question suivante est : comment diable faites-vous ?

Ce que je vais utiliser pour mon composant de script est un moteur de script injectable Lua. Permettez-moi de commencer par dire que je ne suis pas un expert en Lua, mais c'est un langage relativement simple et ne nécessite pas d'apprentissage fastidieux pour le maîtriser. Certains des exemples suivants que je vais passer en revue sont assez simples. À la fin de ce document, je vais inclure quelques documents de base supplémentaires. Pour être honnête, il existe d'autres langages de script, tels que Small, Simkin, Python, Perl. Cependant, Lua est un langage agréable et propre. C'est un très bon avantage.

Lua est open source. C'est bien parce que : (a) vous obtenez le code source du langage et vous pouvez le parcourir autant que vous le souhaitez, (b) c'est gratuit. Vous pouvez l'utiliser dans des applications commerciales sans gaspiller votre argent. Eh bien, pour les projets non commerciaux, vous savez, gratuit == bien.

Alors, qui utilise actuellement Lua ? Lua a été écrit par un bureau de sharashka et n'est utilisé que par les pauvres ? Mmm... pas vraiment. Lua n'est pas apparu hier et a été utilisé par des personnalités assez célèbres :

  • Lucasarts
    • Fandango sinistre
    • S'échapper de l'Île aux Singes
  • Bioware
    • Les nuits de Neverwinter

Ok, assez de qui-est-qui des développeurs Lua. Vous pouvez le constater par vous-même sur le site Web de Lua.

Commençons par quelque chose de vraiment simple. La première chose que nous devons construire nous montrera comment l'interpréteur Lua est utilisé. Ce dont vous aurez besoin pour cela :

  1. Obtenir le code de l'interpréteur Lua.
  2. Mise en place de votre environnement de développement.
  3. Construire un interprète à partir de zéro.

Hé, je pensais que tu en avais assez dit ?

Eh bien, est-ce suffisant ? Alors, passons aux choses sérieuses. Vous pouvez obtenir tout le code source de Lua sur le site officiel. Je voudrais également prendre une seconde et souligner qu'il y a une nouvelle version de Lua 5.0 à l'horizon. Je ne vais pas discuter de cette version dans cet article. J'en parlerai plus tard, mais pour l'instant, nous utiliserons la version 4.0.1.

La première chose que nous ferons est de construire la bibliothèque Lua. De cette façon, nous n’aurons pas besoin d’inclure les sources à chaque fois que nous construisons le projet. Ce n'est pas difficile et ce n'est pas le but de nos cours. C'est pourquoi j'ai inclus la bibliothèque dès le départ dans cet article. J'ai utilisé une bibliothèque statique pour cet exemple. Oui, je le construirais peut-être sous forme de DLL, mais pour un système de script, une bibliothèque statique fonctionne un peu plus rapidement. Attention, pas beaucoup, mais plus vite.

LUÁ, iau, vb. I. trans. I. 1. Un principe un objet dans mână spre a l ţine (şi a se servi de el) ou sau spre a l pune în altă parte. ♢ expr. A lua altă vorbă = a schimba (cu dibăcie) subiectul unei discuţii. A(şi) lua picioarele la spinare = a pleca… … Dicționar Român

Lua- Logo Basisdaten Paradigmen: Skriptsprache, imperativ, funktional, objektorientiert … Deutsch Wikipedia

Lua- [] Información general Paradigma Multiparadigma: performado, imperativo, funcional, orientado a objetos, basado en prototipos Apareció en … Wikipedia Español

LUA- Apparu en 1993 Auteur Luiz Henrique de Figueiredo, Roberto Ierusalimschy et Waldemar Celes Implémentations Lua, LuaJIT, LLVM Lua, LuaCLR, Nua, Lua A … Wikipédia en Français

LUA- (portugiesisch für Mond) il y a une description de scénario dans le programme, une description claire et précise des choses. Une des interprétations particulières de Lua est la grande taille des interprètes de scripts compilés. Lua wurde… …Deutsch Wikipédia

Lua- art. F. 1. Oúnico Planeta Satélite da Terra. 2. Tempo compréendido entre deux novilúnios. 3. Mes. 4. Cio. 5. O mesmo que peixe lua. 6. Disco de ouro ou prata que os Timores usam ao pescoço, como símbolo de… … Dictionnaire de la langue portugaise

Lua- peut faire référence à : * Lua (langage de programmation), un langage de programmation léger et extensible * Lua (Yu Gi Oh! 5D s) * Lua (déesse), la déesse romaine * Lua (art martial), un art martial traditionnel hawaïen * Lua (chanson), un single du folk rock… … Wikipédia

Abréviation LUA : LUA dernier ancêtre universel (également traduit par « Last Universal Ancestor » (anglais LUA, Last Universal Ancestor), sinon Last Universal Common Ancestor (anglais LUCA, dernier universel commun... ... Wikipedia

lúa- (Del gót. lôfa, palma de la mano). 1.f. Especie de guante hecho de sparto y sin separaciones para los dedos, qui servent à nettoyer les caballerías. 2. Mars. Revés de las velas por la parte donde van cazadas con viento largo o en popa. 3. Mars… … Dictionnaire de la langue espagnole

Lua- Lua, römische Göttin, Tochter des Saturnus, welcher nach der Schlacht zur Sühne des vergossenen Blutes erbeutete Waffen von dem Feldherrn verbrannt wurden … Pierer's Universal-Lexikon

LUA- Dea quaedam apud vett, a luendo, expiandoque nomen sortita, quae praeerat lustrationibus, et lustris. Vidéo Turneb. Annonceur s. l. 16. c. 20. et l. 23. c. 23. et l. 19. c. 11. Eius meminit A. Gell. l. 13.c. 22. cum ait in libris Sacerdotum Pop. Rom...Hofmann J. Lexique universel

Livres

  • Programmation en Lua
  • Programmation en langage Lua. Guide, Jérusalem à Robert. Le livre est dédié à l'un des langages embarqués les plus populaires - Lua. Ce langage a été utilisé dans de nombreux jeux et dans de nombreuses applications différentes. La langue combine un petit volume...

Introduction

Ce guide est destiné à ceux qui ont une expérience limitée avec LUA. Nous aborderons les bases du formatage du code, les éléments de base vous permettant de créer un code plus complexe et fournirons quelques exemples. Le manuel est rédigé de manière à pouvoir être mis en pratique immédiatement. Par conséquent, vous devez ouvrir Tabletop Simulator et votre éditeur LUA pour continuer.

Ceci est le premier tutoriel de cette série. La seconde est Apprendre Lua More. Le troisième est un ensemble de fonctions utiles appelées Learning Lua Functions.

Avant la première pression sur une touche

Tout d'abord, je recommanderais fortement d'installer Atom si vous envisagez de créer des scripts dans Tabletop Simulator. Il sait quelles fonctions peuvent être utilisées et importera/exportera le code vers/depuis TTS.

Ensuite, vous devriez ajouter aux favoris. Vous créerez souvent des liens vers ce site une fois que vous aurez commencé à écrire vos scripts. Vous trouverez ici les fonctionnalités spéciales de Tabletop Simulator et leur fonctionnement. Vous utiliserez le plus souvent les pages API et Objet, du moins d'après mon expérience.

Préparation

Lorsque vous enregistrez vos scripts dans Tabletop, il utilisera votre dernière sauvegarde, puis y chargera les scripts. Par conséquent, pour tout script que vous avez l’intention d’écrire, vous devrez procéder comme suit :

  • Préparez la table comme vous le souhaitez.
  • Enregistrez le tableau.
  • Chargez le tableau.
Pour cet exercice, prenez une table vide et créez deux objets (j'ai utilisé un bloc carré et un bloc rectangulaire) ainsi qu'un pion rouge.

Appuyez dessus à plusieurs reprises car bien sûr vous le ferez.

CRÉDIT SUPPLÉMENTAIRE: Lorsque vous créez des tableaux, il existe plusieurs façons de procéder. La technique utilisée ici visait à assurer la clarté visuelle. Cependant, créer des paramètres de bouton comme celui-ci prend beaucoup de place si vous avez beaucoup de boutons. Je préfère concevoir mes tables de manière à économiser de l'espace sans en faire trop. En utilisant notre exemple, je créerais la table d'options comme ceci :

button_parameters = ( click_function="buttonClicked", function_owner=nil, label="Appuyez sur moi", position=(0,0.8,0), rotation=(0,0,0), largeur=500, hauteur=500, font_size= 100)

CRÉDIT SUPPLÉMENTAIRE: C’est le moment idéal pour commencer à jouer avec les différentes choses que l’on peut faire avec les objets. Accédez à la page Objet dans la base de connaissances et essayez le matériel. Déplacez des objets, faites-les changer de position, changez leurs couleurs, quoi que vous en pensiez.

CRÉDIT SUPPLÉMENTAIRE: De plus, chaque fois que vous cliquez sur le bouton, la fonction click_function s'exécute avec deux paramètres. La première est une référence à un objet, plus précisément une référence à l'objet auquel le bouton est lié. La seconde est la couleur (par exemple « Bleu ») sous forme de chaîne de la couleur du joueur qui a appuyé sur le bouton.

5) Énoncé logique

Comparaison des variables

Encore supprimez tous les scripts à l’intérieur de la fonction buttonClicked(). Nous allons créer une nouvelle variable puis la modifier. La nouvelle variable sera de type booléen. Les valeurs booléennes ne peuvent être que vraies ou fausses. Les valeurs booléennes sont toujours écrites en minuscules. Tout d’abord, nous allons créer notre variable sous notre GUID Objets et Vérificateurs.

vraiOuFaux = vrai

Ensuite, dans ButtonClicked, nous mettrons en place une logique pour vérifier si trueOrFalse est vrai. Si c'est vrai, il affichera que c'est vrai et le passera à faux. Si vous appuyez à nouveau sur le bouton, il affichera qu'il est False et changera la valeur en True.

si trueOrFalse alors print("trueOrFalse was true.") --trueOrFalse était true. trueOrFalse = false else print("trueOrFalse était faux.") --trueOrFalse était faux. trueOrFalse = vraie fin

On pourrait aussi l'écrire ainsi "if trueOrFalse == true then", mais ce n'est pas nécessaire. N'oubliez pas que l'instruction IF doit recevoir une valeur booléenne. Et puisque trueOrFalse en fait déjà partie, nous pouvons supprimer le "== true".

Une boucle est une section de code qui peut être exécutée plusieurs fois. C'est l'un des éléments les plus complexes que vous utiliserez dans LUA. Ils sont souvent accompagnés de tableaux, vous permettant d'exécuter du code pour chaque enregistrement du tableau.

Ceci est un autre type - les ipairs. Des paires sont nécessaires pour les tables sans touches numériques, et des paires i sont nécessaires pour les tables avec des touches numériques séquentielles (tableaux). ipairs va dans l'ordre, alors que les paires peuvent aller dans n'importe quel ordre.

Lua vous donne le pouvoir ; vous construisez les mécanismes.
// Roberto Jérusalem


Introduction

Lua est un langage de programmation conçu pour être intégré à d'autres applications afin de permettre à leurs utilisateurs d'écrire des scripts de configuration et des scripts de haut niveau. Lua prend en charge les styles de programmation procédurale, objet et fonctionnelle, mais est en même temps un langage simple. L'interpréteur Lua est écrit en ANSI-C et est une bibliothèque qui peut être connectée à n'importe quel programme. Dans ce cas, le programme de contrôle peut appeler des fonctions de bibliothèque pour exécuter un morceau de code Lua et travailler avec les données définies dans ce code. Le programme de contrôle peut également enregistrer ses propres fonctions afin qu'elles puissent être appelées à partir du code Lua. Cette dernière fonctionnalité permet d'utiliser Lua comme un langage adaptable à n'importe quelle application. Une autre utilisation de Lua consiste à écrire des scripts simples et indépendants. Un simple interpréteur Lua est disponible à cet effet, utilisant cette bibliothèque pour exécuter du code saisi depuis la console ou depuis un fichier.

Conventions lexicales

Les identifiants peuvent contenir des lettres, des chiffres et des traits de soulignement et ne peuvent pas commencer par un chiffre.

Les identifiants commençant par un trait de soulignement et contenant uniquement des majuscules sont réservés à un usage interne par l'interprète.

Les identifiants font la différence entre les lettres majuscules et minuscules.

Les chaînes littérales peuvent être placées entre guillemets simples ou doubles. Les séquences de caractères spéciaux suivantes peuvent y être utilisées :

\n saut de ligne (LF = 0x0a) \a cloche \r retour chariot (CR = 0x0d) \b retour arrière \t tabulation \f saut de page \\ caractère barre oblique inverse \v tabulation verticale \" citation \[ crochet gauche \ " apostrophe \] crochet droit \ddd caractère avec code ddd (décimal) \0 caractère avec code 0

S'il y a une barre oblique inverse à la fin d'une ligne dans le fichier source, alors la définition d'une chaîne littérale peut être poursuivie sur la ligne suivante, avec un caractère de nouvelle ligne inséré à cet endroit.

Les littéraux de chaîne peuvent également être placés entre doubles crochets [[....]] . Dans ce cas, le littéral peut être défini sur plusieurs lignes (les caractères de nouvelle ligne sont inclus dans la chaîne littérale) et aucune séquence de caractères spéciaux n'est interprétée.

S'il y a une nouvelle ligne immédiatement après les caractères "[[", elle n'est pas incluse dans la chaîne littérale.

En tant que délimiteur de ligne, en plus des doubles crochets, le symbole [===[ .... ]===] peut être utilisé, dans lequel un nombre arbitraire de signes égaux est situé entre les crochets répétitifs (le même pour le délimiteur d'ouverture et de fermeture).

Les constantes numériques peuvent spécifier une partie fractionnaire facultative et un exposant décimal facultatif, spécifiés par les caractères "e" ou "E" . Les constantes numériques entières peuvent être spécifiées en notation hexadécimale en utilisant le préfixe 0x.

Le commentaire commence par les caractères "--" (deux moins d'affilée) et se poursuit jusqu'à la fin de la ligne. Si les caractères "--" sont immédiatement suivis des caractères "[[", alors le commentaire est multiligne et continue jusqu'aux caractères "]]". Un commentaire multiligne peut contenir des paires imbriquées de caractères [[....]]. Comme délimiteurs pour les commentaires multilignes, en plus des doubles crochets, le symbole [===[ .... ]===] peut également être utilisé, dans lequel un nombre arbitraire de signes égal est situé entre le carré répétitif parenthèses (idem pour les délimiteurs d'ouverture et de fermeture). La constante de chaîne échappe au début des caractères de commentaire.

Si la première ligne du fichier commence par un caractère "#", elle est ignorée. Cela permet à Lua d'être utilisé comme interpréteur de script sur des systèmes de type Unix.

Types de données

Lua a les types de données suivants :

Nil vide booléen numéro logique chaîne numérique chaîne fonction fonction données utilisateur données utilisateur thread flux table tableau associatif

Le type nil correspond à l'absence de valeur. Ce type a une seule valeur nulle.

Le type booléen a deux valeurs : vrai et faux.

Une valeur nulle est traitée comme false . Toutes les autres valeurs, y compris le nombre 0 et la chaîne vide, sont traitées comme la valeur booléenne true .

Tous les nombres sont représentés sous forme de nombres réels à double précision.

Les chaînes sont des tableaux de caractères de 8 bits et peuvent contenir un caractère nul. Toutes les chaînes de Lua sont constantes, c'est-à-dire Vous ne pouvez pas modifier le contenu d'une ligne existante.

Les fonctions peuvent être affectées à des variables, transmises aux fonctions en tant qu'argument, renvoyées comme résultat d'une fonction et stockées dans des tables.

Le type userdata correspond à un pointeur non typé qui peut être utilisé pour localiser des données arbitraires. Un programme Lua ne peut pas travailler directement avec de telles données (les créer, les modifier). Ce type de données n'a aucune opération prédéfinie autre que la comparaison d'affectation et d'égalité. Dans le même temps, de telles opérations peuvent être définies à l'aide du mécanisme des métaméthodes.

Le type de thread correspond à un thread s’exécutant indépendamment. Ce type de données est utilisé par le mécanisme de coroutine.

Le type de table correspond aux tables - tableaux associatifs qui peuvent être indexés par n'importe quelle valeur et qui peuvent simultanément contenir des valeurs de types arbitraires.

Le type d'un objet stocké dans une variable peut être trouvé en appelant la fonction type(). Cette fonction renvoie une chaîne contenant le nom canonique du type : "nil", "number", "string", "boolean", "table", "function", "thread", "userdata" .

Les conversions entre nombres et chaînes se produisent automatiquement lorsqu'elles sont utilisées dans le contexte approprié.

Les opérations arithmétiques impliquent des arguments numériques, et tenter d'effectuer une telle opération sur des chaînes entraînera leur conversion en nombres. Les opérations de chaîne effectuées sur les nombres entraînent leur conversion en chaîne à l'aide d'une conversion de format fixe.

Vous pouvez également convertir explicitement un objet en chaîne à l'aide de la fonction tostring() ou en nombre à l'aide de la fonction tonumber(). Pour avoir plus de contrôle sur le processus de conversion des nombres en chaînes, vous devez utiliser la fonction de conversion de format.

Variables

En Lua, les variables n'ont pas besoin d'être déclarées. Une variable apparaît la première fois qu'elle est utilisée. Si une variable utilisée n'a pas été préalablement initialisée, elle a la valeur nil . Les variables n'ont pas de type statique ; le type d'une variable est déterminé par sa valeur actuelle.

Une variable est considérée comme globale sauf si elle est explicitement déclarée locale. La déclaration des variables locales peut être située n'importe où dans le bloc et peut être combinée avec leur initialisation :

Local x, y, z local a, b, c = 1, 2, 3 local x = x

Lors de l'initialisation d'une variable locale à droite du signe égal, la variable d'entrée n'est pas encore disponible et la valeur d'une variable externe au bloc courant est utilisée. C'est pourquoi l'exemple de la troisième ligne est correct (il démontre un idiome fréquemment utilisé dans la langue).

Pour les variables locales, l'interpréteur utilise des portées lexicales, c'est-à-dire La portée d'une variable s'étend de l'endroit où elle est déclarée (première utilisation) jusqu'à la fin du bloc actuel. Dans ce cas, une variable locale est visible dans les blocs internes au bloc dans lequel elle est décrite. La variable locale disparaît lorsqu'elle est hors de portée. Si une variable locale est définie en dehors d'un bloc, alors une telle variable disparaît à la fin de l'exécution de cette section de code, puisque la section de code est exécutée par l'interpréteur comme une fonction sans nom. Une variable globale initialisée existe tant que l'interpréteur est en cours d'exécution.

Pour supprimer une variable, vous pouvez simplement lui attribuer la valeur nil .

Les tableaux, les fonctions et les données utilisateur sont des objets. Tous les objets sont anonymes et ne peuvent pas être la valeur d'une variable.

Les variables stockent les références aux objets. Lors de l'affectation, du passage à une fonction en tant qu'argument et du retour de la fonction en conséquence, les objets ne sont pas copiés, seules les références à eux sont copiées.

les tables

Les tables (type table) correspondent à des tableaux associatifs qui peuvent être indexés avec toutes valeurs autres que nil et qui peuvent simultanément contenir des valeurs de types arbitraires autres que nil . Les éléments du tableau peuvent également être indexés par des objets - tableaux, fonctions et objets de type userdata. Les éléments du tableau auxquels aucune valeur n'est attribuée par défaut sont nil .

Les tableaux sont la principale structure de données dans Lua. Ils sont également utilisés pour représenter des structures, des classes et des objets. Dans ce cas, l'indexation par le nom de chaîne du champ de structure est utilisée. Puisqu’un élément de tableau peut être une fonction, les méthodes sont également autorisées dans les structures.

Les crochets sont utilisés pour indexer les tableaux : array . L'entrée struct.field est équivalente à l'entrée suivante : struct["field"] . Cette fonctionnalité syntaxique permet d'utiliser des tables comme enregistrements avec des champs nommés.

Un constructeur de table est une expression qui crée et renvoie une nouvelle table. Chaque exécution du constructeur crée une nouvelle table. Le constructeur de table est une liste d'initialiseurs de champ (éventuellement vides) entourés d'accolades, séparés par une virgule ou le symbole ";". (point-virgule). Les options suivantes sont valides pour les initialiseurs de champ :

Table Exp2 = nom exp2 = table exp["nom"] = exp table exp[j] = exp

Dans ce dernier cas, la variable j parcourt des valeurs entières successives, en commençant par 1. Les initialiseurs des deux premiers types ne modifient pas la valeur de ce compteur. Voici un exemple de construction de table :

X = (len = 12, 11, 12, = 1123)

Après avoir exécuté un tel opérateur, les champs du tableau recevront les valeurs suivantes :

X["len"] = x.len = 12 x = 11 x = 12 x = 1123

Si le dernier élément de la liste d'initialisation est un appel de fonction, alors les valeurs de retour de la fonction sont placées séquentiellement dans la liste d'initialisation. Ce comportement peut être modifié en mettant l'appel de fonction entre parenthèses. Dans ce cas, de toutes les valeurs renvoyées par la fonction, seule la première est utilisée.

Le dernier initialiseur peut être suivi d'un caractère séparateur d'initialiseur de champ facultatif (virgule ou point-virgule).

Opérations

Les principales opérations sont listées ci-dessous :

Changement de signe + - * / arithmétique ^ exponentiation == ~= égalité< <= >>= pas d'ordre et ou logique.. concaténation de chaînes # obtenir la longueur d'une chaîne ou d'un tableau

Lors de l'utilisation d'opérations arithmétiques, les chaînes ayant une valeur numérique y sont converties. Lorsque vous concaténez des valeurs numériques, elles sont automatiquement converties en chaînes.

Les comparaisons d'égalité n'effectuent pas de conversion de type. Les objets de types différents sont toujours considérés comme différents.

En conséquence, "0" ~= 0 , et lors de l'indexation, a et a["0"] correspondent à différentes cellules du tableau. Lors de la comparaison d'objets pour l'égalité/inégalité, les références aux objets sont comparées. Les variables qui font référence au même objet sont égales.

Lors de la détermination de l'ordre, les types d'arguments doivent correspondre, c'est-à-dire les nombres sont comparés aux nombres et les chaînes sont comparées aux chaînes.

Les relations d'égalité et d'ordre aboutissent toujours à vrai ou faux, c'est-à-dire valeur booléenne.

Dans les opérations booléennes, nil est traité comme faux et toutes les autres valeurs, y compris le nombre nul et la chaîne vide, sont traitées comme vraies.

Lors du calcul de la valeur, un schéma court est utilisé - le deuxième argument n'est calculé que si nécessaire.

Le tableau suivant des priorités et de l’associativité des opérations s’applique :

^pas # -(unaire) * / + -< > <= >= ~= == .. et ou

Opérations logiques et idiomes associés

L'opérateur not renvoie toujours une valeur booléenne, prenant un argument de type arbitraire (dans ce cas, seule la valeur nil correspond à la valeur booléenne false , le reste est traité comme vrai). En revanche, les opérateurs and et or renvoient toujours un de leurs arguments. L'opérateur ou renvoie son premier argument si sa valeur n'est pas fausse ou nulle, et son deuxième argument dans le cas contraire. L'opérateur and renvoie son premier argument si sa valeur est fausse ou nulle et son deuxième argument dans le cas contraire. Ce comportement est basé sur le fait que toutes les valeurs autres que nil sont traitées comme vraies.

Il existe plusieurs idiomes courants associés à ce comportement. Le tableau suivant montre l'opération idiomatique à gauche et la notation régulière équivalente à droite :

X = x ou v si x == nul alors x = v fin x = (e et a) ou b si e ~= nul alors x = a sinon x = b fin

Le premier idiome est souvent utilisé pour attribuer une valeur par défaut à une variable non initialisée. Le deuxième idiome est équivalent à l'opérateur C x = e ? a, b (on suppose ici que la valeur de la variable a n'est pas nulle).

Les opérateurs

Dans Lua, il n'y a pas de fonction dédiée à partir de laquelle commence l'exécution du programme. L'interpréteur exécute séquentiellement les instructions qu'il reçoit d'un fichier ou d'un programme de contrôle. Ce faisant, il précompile le programme en une représentation binaire qui peut également être enregistrée. Tout bloc de code s'exécute comme une fonction anonyme, il peut donc définir des variables locales et en renvoyer une valeur.

Les opérateurs peuvent (mais ce n'est pas obligatoire) être séparés par le symbole « ; » .

Plusieurs missions sont autorisées :

Var1, var2 = val1, val2

Dans ce cas, l'alignement est effectué - les valeurs supplémentaires sont supprimées et les variables correspondant aux valeurs manquantes se voient attribuer la valeur nulle. Toutes les expressions du côté droit d'une affectation multiple sont évaluées avant l'affectation elle-même.

Si une fonction renvoie plusieurs valeurs, en utilisant plusieurs affectations, vous pouvez obtenir les valeurs de retour :

X, y, z = f(); une, b, c, d = 5, f();

En général, s'il y a un appel de fonction à la fin de la liste de valeurs située à droite du signe d'affectation, alors toutes les valeurs renvoyées par la fonction sont ajoutées à la fin de la liste de valeurs. Ce comportement peut être modifié en mettant l'appel de fonction entre parenthèses. Dans ce cas, de toutes les valeurs renvoyées par la fonction, seule la première est utilisée.

Les principaux opérateurs sont listés ci-dessous :

Faire ... terminer si ... alors ... terminer si ... alors ... sinon ... terminer si ... alors ... elseif ... alors ... terminer si ... alors . .. elseif ... then ... else ... end while ... do ... end répéter ... jusqu'à ... pour var = start, stop do ... end pour var = start, stop, étape faire ... fin retour retour ... pause

Le bloc do ... end transforme une séquence d'instructions en une seule instruction et ouvre une nouvelle portée dans laquelle des variables locales peuvent être définies.

Dans les instructions if, while et repeat, toutes les valeurs d'expression autres que false et nil sont traitées comme vraies.

Voici la forme générale d’écriture d’une instruction if :

Si... alors... (sinon... alors...) fin

L'instruction return peut ne contenir aucune valeur de retour ou peut contenir une ou plusieurs expressions (liste). Étant donné que le bloc de code est exécuté en tant que fonction anonyme, la valeur de retour peut provenir non seulement de la fonction, mais également d'un bloc de code arbitraire.

Les instructions return et break doivent être les dernières instructions du bloc (c'est-à-dire qu'elles doivent être soit les dernières instructions du bloc de code, soit être situées immédiatement avant les mots end , else , elseif , jusqu'à ). Dans un bloc, vous devez utiliser l’idiome do return end ou do break end.

L'instruction for est exécutée pour toutes les valeurs de la variable de boucle, de la valeur de début à la valeur de fin, inclusivement. La troisième valeur, si elle est spécifiée, est utilisée comme étape de changement de variable de boucle. Toutes ces valeurs doivent être numériques. Ils ne sont évalués qu'une seule fois avant l'exécution de la boucle. Une variable de boucle est locale à la boucle et n'est pas accessible en dehors du corps de la boucle. La valeur d'une variable de boucle ne peut pas être modifiée dans le corps d'une boucle. Voici le pseudocode démontrant l'exécution d'une instruction for :

Faites local var, _limit, _step = tonumber(start), tonumber(stop), tonumber(step) sinon (var et _limit et _step) alors error() se termine tandis que (_step>0 et var<=_limit) or (_step<=0 and var>=_limit) faire ... var = var + _step fin fin

Les fonctions

Une définition de fonction est une expression exécutable (constructeur de fonction), dont le résultat est un objet de type fonction :

F = fonction(...) ... fin

Une liste (éventuellement vide) d'arguments de fonction est placée entre parenthèses. La liste des arguments de la fonction peut se terminer par des points de suspension - dans ce cas, la fonction a un nombre variable d'arguments. Le corps de la fonction est placé entre la parenthèse fermante et l'instruction de fin.

Les formulaires courts suivants sont disponibles pour définir une fonction :

Fonction fname(...) ... fin fname = fonction(...) ... fin locale fonction fname(...) ... fin locale fname = fonction(...) ... fin fonction x .fname(...) ... fin x.fname = fonction(...) ... fin fonction x:fname(...) ... fin x.fname = fonction(self, ...) ...fin

Lorsque le constructeur de fonction est exécuté, il est également construit court-circuit- un tableau de toutes les variables locales disponibles dans la fonction et externes à celle-ci. Si une fonction est passée comme valeur de retour, elle conserve alors l'accès à toutes les variables incluses dans sa fermeture. Chaque fois que le constructeur de fonction est exécuté, une nouvelle fermeture est construite.

Un appel de fonction se compose d’une référence de fonction et d’une liste d’arguments (éventuellement vide) entre parenthèses. Une référence de fonction peut être n’importe quelle expression dont l’évaluation aboutit à une fonction. Il ne peut pas y avoir de nouvelle ligne entre une référence de fonction et une parenthèse ouvrante.

Les formulaires courts suivants sont disponibles pour appeler des fonctions :

F(...) f((...)) f("...") f"..." f("") f"" f([[...]]) f[[. ..]] x:f(...) x.f(x, ...)

Dans le premier cas, le seul argument est une table construite à la volée, et dans les trois cas suivants, une chaîne littérale. Dans ce dernier cas, x est utilisé comme table à partir de laquelle la variable f est récupérée, qui est une référence à la fonction appelée, et la même table est transmise à la fonction comme premier argument. Cette fonctionnalité syntaxique est utilisée pour implémenter des objets.

Lorsqu'une fonction est appelée, les valeurs de types simples sont copiées dans les arguments par valeur, et pour les objets, les références sont copiées dans les arguments. Lors de l'appel d'une fonction, le nombre d'arguments est égalisé - les valeurs excédentaires sont ignorées et les arguments correspondant aux valeurs manquantes reçoivent la valeur nulle. Si à la fin de la liste des paramètres se trouve un appel de fonction qui renvoie plusieurs valeurs, alors toutes sont ajoutées à la liste des arguments. Ce comportement peut être modifié en mettant l'appel de fonction entre parenthèses. Dans ce cas, de toutes les valeurs renvoyées par la fonction, seule la première est utilisée.

Si une fonction a un nombre variable d'arguments, alors les valeurs qui ne sont affectées à aucun des arguments peuvent être obtenues en utilisant un nom spécifique... . Ce nom se comporte comme un ensemble de valeurs renvoyées par une fonction c'est-à-dire lorsqu'elle apparaît à l'intérieur d'une expression ou au milieu d'une liste de valeurs, seule la première valeur de retour est utilisée. Si le nom... apparaît en dernière position de la liste de valeurs (dans les constructeurs de tables, avec plusieurs affectations, dans la liste des arguments lors de l'appel d'une fonction et dans l'instruction return), alors toutes les valeurs renvoyées sont placés à la fin de cette liste (règle de complétion de liste). Pour supprimer ce comportement, le nom... peut être placé entre parenthèses. Vous pouvez transformer un ensemble de valeurs en une liste en utilisant l'idiome (...) .

Si une fonction prend un seul argument et le traite comme une table dont les éléments sont indexés par les noms des paramètres formels de la fonction, alors dans ce cas un appel au mécanisme d'argument nommé est effectivement implémenté :

Fonction rename(arg) arg.new = arg.new ou arg.old .. ".bak" return os.rename(arg.old, arg.new) end rename( old = "asd.qwe" )

Une fonction renvoie à la fois lorsque son corps termine son exécution et lorsque l'instruction return est exécutée. L'instruction return peut renvoyer une ou plusieurs valeurs. Si à la fin de la liste des valeurs de retour il y a un appel à une fonction qui renvoie plusieurs valeurs, alors toutes sont ajoutées à la liste des arguments. Ce comportement peut être modifié en mettant l'appel de fonction entre parenthèses. Dans ce cas, de toutes les valeurs renvoyées par la fonction, seule la première est utilisée.

Si une fonction est appelée sous forme d'instruction, alors toutes les valeurs de retour sont détruites. Si une fonction est appelée depuis une expression ou depuis le milieu d'une liste de valeurs, alors seule la première valeur de retour est utilisée. Si une fonction est appelée depuis la dernière position d'une liste de valeurs (dans les constructeurs de table, avec plusieurs affectations, dans la liste d'arguments lors de l'appel d'une fonction et dans l'instruction return), alors toutes les valeurs renvoyées sont placées à la fin de cette liste (règle de complétion de liste). Pour supprimer ce comportement, l'appel de fonction peut être placé entre parenthèses.

Il existe une fonction prédéfinie unpack() qui prend un tableau dont les éléments sont indexés à 1 et renvoie tous ses éléments. Cette fonction peut être utilisée pour appeler des fonctions qui prennent un nombre variable d'arguments, générant dynamiquement une liste d'arguments réels. L'opération inverse s'effectue de la manière suivante :

List1 = (f()) -- crée une liste à partir de toutes les valeurs renvoyées par f() list2 = (...) -- crée une liste à partir de toutes les valeurs transmises à une fonction variadique

Un nombre variable de valeurs de retour, une règle de complétion de liste et la possibilité de transmettre tous les paramètres formels à une fonction peuvent interagir de manière non triviale. Souvent, une fonction (par exemple foo()) renvoie une réponse si elle renvoie normalement et nil et un message d'erreur si ce n'est pas le cas. La fonction assert(val, msg) génère un message d'erreur (en appelant la fonction error(msg)) si val est false ou nil et renvoie val sinon. Puis l'opérateur

V = assert(foo(), "message")

en cas de succès, attribue à la variable v la valeur renvoyée par foo() . Dans ce cas, foo() renvoie une valeur et assert() reçoit un paramètre msg égal à nil . Si une erreur se produit, la fonction assert() reçoit nil et un message d'erreur.

Itérateurs

Les itérateurs sont utilisés pour énumérer des éléments de séquences arbitraires :

Pour v_1, v_2, ..., v_n dans explist, faites ... fin

Le nombre de variables dans la liste v_1, ..., v_n peut être arbitraire et ne doit pas nécessairement correspondre au nombre d'expressions dans la liste explicite. Le rôle d'explist est généralement un appel à une fonction de fabrique d'itérateurs. Une telle fonction renvoie une fonction itératrice, l'état et la valeur initiale de la variable de contrôle de boucle. L'itérateur est interprété comme suit :

Do local f, s, v_1 ​​​​= explist local v_2, ... , v_n while true do v_1, ..., v_n = f(s, v_1) if v_1 == nil then break end ... end end

A chaque étape, les valeurs de toutes les variables v_k sont calculées en appelant la fonction itérateur. La valeur de la variable de contrôle v_1 contrôle la fin de la boucle - la boucle se termine dès que la fonction itératrice renvoie nil comme valeur de la variable var_1 .

En fait, les itérations sont contrôlées par la variable v_1 , et les variables restantes peuvent être considérées comme la « queue » renvoyée par la fonction itérateur :

Faire local f, s, v = explist tandis que vrai faire v = f(s, v) si v == nil alors break end ... end end

L'instruction dans laquelle la fonction factory est appelée est interprétée comme un opérateur d'affectation régulier, c'est-à-dire Une fonction d'usine peut renvoyer un nombre arbitraire de valeurs.

Itérateurs sans état interne

Un itérateur sans état interne ne stocke aucune information interne lui permettant de déterminer sa position dans le conteneur itérable. La valeur suivante de la variable de contrôle est calculée directement à partir de sa valeur précédente et l'état est utilisé pour stocker une référence au conteneur itérable. Voici un exemple d'itérateur simple sans état interne :

Fonction iter(a, i) i = i + 1 local v = a[i] si v alors renvoie i, v sinon renvoie nil end end fonction ipairs(a) renvoie iter, a, 0 end

Itérateurs stockant l'état dans une fermeture

Si un itérateur a besoin d'un état interne pour parcourir son conteneur, le moyen le plus simple est de le stocker dans une fermeture créée à partir du contexte de la fonction factory. Voici un exemple simple :

Fonction ipairs(a) local i = 0 local t = une fonction locale iter() i = i + 1 local v = t[i] si v alors renvoie i, v sinon renvoie nul fin fin renvoie iter fin

Ici, l'itérateur stocke l'intégralité du contexte dans la fermeture et n'a pas besoin de l'état et de la valeur actuelle de la variable de contrôle. En conséquence, l'itérateur n'accepte pas d'état et de variable de contrôle, et la fabrique ne renvoie pas la valeur d'état ni la valeur de départ de la variable de contrôle.

Itérateurs standards

Le plus souvent, les itérateurs sont utilisés pour parcourir les éléments du tableau. Il existe plusieurs fonctions prédéfinies d’usine d’itérateurs à cet effet. La fabrique paires(t) renvoie un itérateur qui donne à chaque étape l'index dans la table et la valeur située à cet index :

Pour idx, val in pairs(tbl) do ... end

En fait, cet itérateur est facile à définir à l'aide de la fonction standard next(tbl, idx) :

Les paires de fonctions (tbl) renvoient next, tbl, nil end

La fonction next(tbl, idx) renvoie la valeur d'index suivante après idx lors d'un parcours de la table tbl (l'appel next(tbl, nil) renvoie la valeur d'index initiale ; une fois les éléments de la table épuisés, nil est renvoyé).

La fabrique ipairs(tbl) renvoie un itérateur qui fonctionne exactement comme celui décrit ci-dessus, mais est conçu pour parcourir des tables indexées par des entiers commençant à 1 .

Métatables

Chaque table et objet de type userdata peut avoir une méta-table - une table normale dont les champs déterminent le comportement de l'objet d'origine lorsque certaines opérations spéciales lui sont appliquées. Par exemple, lorsqu'un objet est l'opérande d'addition, l'interpréteur recherche dans la méta-table un champ nommé __add et, si un tel champ est présent, utilise sa valeur comme fonction qui effectue l'addition. Les métatables vous permettent de définir le comportement d'un objet lors d'opérations arithmétiques, de comparaisons, de concaténation et d'indexation. Vous pouvez également définir une fonction appelée lorsqu'un objet de type userdata est publié. Les index (noms de champs) dans une méta-table sont appelés événements, et les valeurs correspondantes (gestionnaires d'événements) sont méta-méthodes.

Par défaut, une table nouvellement créée n'a pas de méta-table. N'importe quelle table mt peut devenir une méta-table de la table t en appelant setmetatable(t, mt) . La fonction getmetatable(t) renvoie la méta-table de la table t, ou nil si la table n'a pas de méta-table. N'importe quelle table peut servir de méta-table pour n'importe quelle autre table, y compris elle-même.

Lua définit les événements suivants :

Ajouter, __sub, __mul, __div opérations arithmétiques __pow exponentiation __unm unaire moins __concat concaténation __eq, __lt, __le opérations de comparaison __index accès par index manquant __newindex affectation à un nouvel élément de table __call appel de fonction __tostring conversion en une chaîne __metatable obtention d'une méta-table

L'expression a ~= b est évaluée comme not (a == b) . L'expression a > b est évaluée à b< a . Выражение a >= b est calculé comme b<= a . При отсутствии метаметода __le операция <= вычисляется как not (b < a) т.е. с помощью метаметода __lt .

Pour les opérations binaires, le gestionnaire est sélectionné comme suit : le premier opérande est interrogé et, s'il ne spécifie pas de gestionnaire, alors le deuxième opérande est interrogé. Pour les opérations de comparaison, une métaméthode est sélectionnée uniquement si les opérandes comparés ont le même type et la même métaméthode pour effectuer l'opération. Le manuel d'utilisation fournit un pseudocode Lua démontrant le contexte d'appel des métaméthodes.

Le gestionnaire d'événements __index peut être une fonction ou une table. Dans le cas d'une fonction, le gestionnaire est appelé et la table et la valeur de l'index lui sont transmises. Une telle fonction doit renvoyer le résultat de l'indexation. Dans le cas d'une table, la table est réindexée avec le même index. Si le gestionnaire d'événements __newindex est présent, il est appelé au lieu d'attribuer une valeur au nouvel élément de table. Si ce gestionnaire est une table, alors l'affectation est effectuée sur cette table.

La métaméthode __tostring vous permet de gérer la conversion d'un objet (table ou données utilisateur) en chaîne. La métaméthode __metatable vous permet de gérer l'opération d'obtention d'une méta-table. Si ce champ dans la méta-table a une valeur définie, alors la fonction getmetatable() renverra la valeur de ce champ et la fonction setmetatable() échouera.

Exemples d'utilisation de métatables

Valeur par défaut pour les champs de table

L'exemple suivant utilise des métatables pour attribuer une valeur par défaut aux éléments de table manquants :

Fonction set_def(t, v) local mt = ( __index = function() return v end ) setmetatable(t, mt) end

Voici une solution plus simple qui n'utilise pas de méta-table distincte pour chaque valeur par défaut :

Clé locale = () local mt = ( __index = function(t) return t end ) function set_def(t, v) t = v setmetatable(t, mt) end

Ici, la clé de table locale (vide) est utilisée comme un index évidemment unique par rapport auquel la valeur par défaut v est stockée dans la table source t. Toutes les tables définies sur une valeur par défaut pour les champs manquants partagent une méta-table commune mt. La méta-méthode __index de cette méta-table intercepte les appels aux champs de table manquants et renvoie la valeur stockée dans la table elle-même au niveau de l'index clé.

Cette solution présente un inconvénient : une nouvelle paire clé-valeur apparaît dans le tableau, qui apparaîtra si vous essayez de parcourir tous les éléments du tableau.

Tableau proxy

Dans l'exemple suivant, une table vide agit comme un proxy, transférant les appels vers les champs de la table :

Clé locale = () local mt = ( __index = fonction(t,k) retour t[k] fin, __newindex = fonction(t,k,v) t[k] = v fin) fonction proxy(t) proxy local = () proxy = t setmetatable(proxy, mt) renvoie la fin du proxy

Ici, la clé de table locale (vide) est utilisée comme un index évidemment unique, par lequel un lien vers la table d'origine t est stocké dans la table proxy. Un proxy est une table dont le seul élément est indexé par key , donc l'accès à n'importe quel élément du proxy entraînera l'appel d'une méta-méthode. La méta-table commune à tous les proxys définit les méta-méthodes __index et __newindex, qui récupèrent la table source à partir d'un seul élément proxy, en l'indexant avec la table de clés.

Les méta-méthodes peuvent fournir une discipline arbitraire pour le traitement des appels aux champs de la table source. Des exemples simples sont la journalisation des accès ou la génération d’une erreur lorsque vous essayez de modifier la valeur d’un élément de table.

Des tableaux "faibles"

Si un objet a été utilisé comme index de table ou si une référence à celui-ci a été stockée dans la table, le ramasse-miettes ne pourra pas se débarrasser d'un tel objet. En même temps, dans certains cas, il est souhaitable d'avoir un tableau dans lequel la connexion de ses éléments avec des clés et/ou des valeurs est « faible », c'est-à-dire n'interfère pas avec la collecte des déchets. Généralement, ce besoin survient lors de la mise en cache des résultats de calculs dans une table et du stockage des attributs d'objet dans une table indexée par les objets eux-mêmes. Dans le premier cas, une connexion faible est souhaitable pour les valeurs, dans le second pour les indices.

La relation des éléments de la table avec les objets (valeurs et index) est déterminée par la valeur du champ de chaîne __mode de sa méta-table. Si ce champ contient le caractère "k", alors la connexion pour les indices (clés) est rendue faible ; s'il contient le symbole "v", alors la connexion pour les valeurs est rendue faible. Un champ peut contenir les deux caractères, ce qui rend le couplage faible pour les index et les valeurs.

Si vous utilisez des tables faibles, alors pour le problème ci-dessus de correspondance d'une table avec une valeur par défaut pour les champs manquants, vous pouvez donner la solution suivante :

Valeurs par défaut locales = () setmetatable(defaults, ( __mode = "k" )) local mt = ( __index = function(t) return defaults[t] end ) function set_def(t, d) defaults[t] = d setmetatable(t , mt) fin

Voici une autre solution, où la table faible stocke les méta-tables qui ont le même nombre de valeurs par défaut distinctes :

Metas locales = () setmetatable(metas, ( __mode = "v" )) function set_def(t, d) local mt = metas[d] si mt == nil alors mt = ( __index = function () return d end ) metas [d] = fin mt setmetatable(t, mt) fin

Contexte mondial

Toutes les variables globales sont des champs d'une table régulière appelée contexte mondial. Ce tableau est accessible via la variable globale _G. Puisque toutes les variables globales sont des champs de contexte, alors _G._G == _G .

Le contexte global permet d'accéder aux variables globales par un nom généré dynamiquement :

Val = _G _G = val

Puisque le contexte global est une table normale, il peut avoir une méta-table correspondante. L'exemple suivant introduit les variables d'environnement dans la portée globale en tant que variables globales en lecture seule :

Local f = fonction (t,i) return os.getenv(i) end setmetatable(_G, (__index=f))

La même technique vous permet de refuser l'accès aux variables globales non initialisées.

Paquets

Les packages constituent le principal moyen de définir un ensemble de fonctions associées sans polluer la portée globale. Généralement, un package est un fichier unique qui, dans sa portée globale, définit une seule table contenant toutes les fonctions de ce package :

Mon_package = () function mon_package.foo() ... fin

Vous pouvez également rendre toutes les fonctions locales et créer séparément un tableau des fonctions exportées :

Fonction locale foo() ... fin de la fonction locale bar() ... fin de mon_package = ( foo = foo, bar = bar, )

Le package est chargé à l'aide de la fonction require(), et au moment du chargement, le nom passé à cette fonction (qui ne peut pas contenir d'extension, qui est ajoutée automatiquement) est disponible via la variable _REQUIREDNAME :

Si _REQUIREDNAME == nil alors run_some_internal_tests() se termine

Classes et objets

La construction tbl:func() (à la fois lors de la déclaration d'une fonction et lors de son appel) fournit des fonctionnalités de base qui vous permettent de travailler avec une table en tant qu'objet. Le principal problème est de générer de nombreux objets ayant un comportement similaire, c'est-à-dire généré à partir d'une classe :

Function class() cl = () cl.__index = cl -- cl sera utilisé comme méta-table return cl end function object(cl, obj) obj = obj or () -- il se peut qu'il y ait déjà des champs remplis dans setmetatable( obj, cl ) retourner obj fin

Ici, la fonction de classe crée une table vide, prête à devenir la méta-table de l'objet. Les méthodes de classe sont constituées de champs de cette table, c'est-à-dire une classe est une table qui contient simultanément les méthodes d'un objet et ses méta-méthodes. La fonction object() crée un objet de la classe donnée - une table dont la méta-table est définie sur la classe donnée. Le deuxième argument peut être un tableau contenant les champs initialisés de l'objet.

Some_Class = fonction class() Some_Class:foo() ... fin de la fonction Some_Class:new() return object(self, ( xxx = 12 )) end x = Some_Class:new() x:foo()

Héritage

Dans l'implémentation décrite, la méta-table de classe reste inutilisée, ce qui simplifie la tâche d'implémentation de l'héritage. La classe descendante est créée en tant qu'objet de classe, après quoi elle définit le champ __index afin qu'il puisse être utilisé comme méta-table :

Function subclass(pcl) cl = pcl:new() -- crée une instance cl.__index = cl -- et en fait une classe return cl end

Vous pouvez désormais ajouter de nouveaux champs et méthodes à la classe descendante résultante :

Der_Class = fonction sous-classe (Some_Class) Der_Class:new() local obj = object(self, Some_Class:new()) obj.yyy = 13 -- ajouter de nouveaux champs return obj end function Der_Class:bar() ... end -- et de nouvelles méthodes y = Der_Class:new() y:foo() y:bar()

Le seul point non trivial ici est d'utiliser la fonction new() de la classe ancêtre puis de remplacer la méta-table en appelant la fonction object().

Lors de l'accès aux méthodes d'un objet d'une classe descendante, elles sont d'abord recherchées dans la méta-table, c'est-à-dire dans la classe successeur elle-même. Si la méthode a été héritée, alors cette recherche échouera et la méta-table de la classe héritière sera accédée, c'est-à-dire à la classe des ancêtres.

Le principal inconvénient de la solution générale donnée est l’impossibilité de passer des paramètres à la fonction new() de la classe ancêtre.

Arguments de ligne de commande

Les arguments de ligne de commande transmis au démarrage sont disponibles en tant qu'éléments du tableau arg.