Lua ( / ˈl uːə / LOO -ə ; du portugais : lua [ˈlu(w)ɐ] signifiant lune ) est un langage de programmation léger , de haut niveau et multi-paradigme conçu principalement pour une ...
Lua a été créé en 1993 comme langage d'extension d'applications logicielles pour répondre à la demande croissante de personnalisation de l'époque. Il fournissait les fonctionnalités de base de la plupart des langages de programmation procédurale , mais ne proposait pas de fonctionnalités plus complexes ou spécifiques à un domaine . Il incluait plutôt des mécanismes d'extension du langage, permettant aux programmeurs d'implémenter de telles fonctionnalités. Comme Lua était destiné à être un langage d'extension intégrable général, les concepteurs de Lua se sont concentrés sur l'amélioration de sa vitesse , de sa portabilité , de son extensibilité et de sa facilité d'utilisation lors du développement.
De 1977 à 1992, le Brésil a mis en place une politique de fortes barrières commerciales (appelées « réserve de marché ») pour le matériel informatique et les logiciels , estimant que le Brésil pouvait et devait produire son propre matériel et ses propres logiciels. Dans ce contexte, les clients de Tecgraf ne pouvaient pas se permettre, ni politiquement ni financièrement, d'acheter des logiciels personnalisés à l'étranger ; dans le cadre de la réserve de marché, les clients devaient passer par un processus bureaucratique complexe pour prouver que leurs besoins ne pouvaient pas être satisfaits par les entreprises brésiliennes. Ces raisons ont conduit Tecgraf à mettre en œuvre les outils de base dont elle avait besoin à partir de zéro.
Les prédécesseurs de Lua étaient les langages de description/configuration de données Simple Object Language (SOL) et le langage de saisie de données (DEL). Ils avaient été développés indépendamment chez Tecgraf en 1992-1993 pour ajouter une certaine flexibilité à deux projets différents (tous deux étaient des programmes graphiques interactifs pour des applications d'ingénierie chez Petrobras ). Il manquait des structures de contrôle de flux dans SOL et DEL, et Petrobras ressentait un besoin croissant de leur ajouter toute la puissance de programmation.
Dans The Evolution of Lua , les auteurs du langage ont écrit :
En 1993, le seul véritable concurrent était Tcl , qui avait été explicitement conçu pour être intégré dans des applications. Cependant, Tcl avait une syntaxe peu familière, n'offrait pas un bon support pour la description des données et ne fonctionnait que sur des plates-formes Unix. Nous n'avons pas envisagé LISP ou Scheme en raison de leur syntaxe peu conviviale. Python en était encore à ses balbutiements. Dans l'atmosphère de libre-service qui régnait alors chez Tecgraf, il était tout naturel que nous essayions de développer notre propre langage de script... Comme de nombreux utilisateurs potentiels du langage n'étaient pas des programmeurs professionnels, le langage devait éviter une syntaxe et une sémantique cryptiques. L'implémentation du nouveau langage devait être hautement portable, car les clients de Tecgraf disposaient d'une collection très diversifiée de plates-formes informatiques. Enfin, comme nous nous attendions à ce que d'autres produits Tecgraf aient également besoin d'intégrer un langage de script, le nouveau langage devait suivre l'exemple de SOL et être fourni sous forme de bibliothèque avec une API C.
Lua 1.0 a été conçu de telle manière que ses constructeurs d'objets, étant alors légèrement différents du style léger et flexible actuel, incorporaient la syntaxe de description de données de SOL (d'où le nom Lua : Sol signifiant « Soleil » en portugais, et Lua signifiant « Lune »). La syntaxe Lua pour les structures de contrôle était principalement empruntée à Modula ( if, while, repeat/ until), mais avait également été influencée par CLU (affectations multiples et retours multiples à partir d'appels de fonction, comme une alternative plus simple aux paramètres de référence ou aux pointeurs explicites ), C++ (« idée intéressante de permettre à une variable locale d'être déclarée uniquement là où nous en avons besoin » ), SNOBOL et AWK ( tableaux associatifs ). Dans un article publié dans le Dr. Dobb's Journal , les créateurs de Lua déclarent également que LISP et Scheme avec leur mécanisme de structure de données unique et omniprésent (la liste ) ont eu une influence majeure sur leur décision de développer la table comme structure de données principale de Lua.
La sémantique Lua a été de plus en plus influencée par Scheme au fil du temps, notamment avec l'introduction de fonctions anonymes et d'une portée lexicale complète . Plusieurs fonctionnalités ont été ajoutées dans les nouvelles versions de Lua.
Les versions de Lua antérieures à la version 5.0 étaient publiées sous une licence similaire à la licence BSD . À partir de la version 5.0, Lua est sous licence MIT . Ces deux licences sont des licences de logiciel libre permissives et sont presque identiques.
En général, Lua s'efforce de fournir des méta-fonctionnalités simples et flexibles qui peuvent être étendues selon les besoins, plutôt que de fournir un ensemble de fonctionnalités spécifiques à un paradigme de programmation. En conséquence, le langage de base est léger ; l' interpréteur de référence complet ne pèse qu'environ 247 Ko compilés et est facilement adaptable à une large gamme d'applications.
Un commentaire en Lua commence par un double trait d'union et se poursuit jusqu'à la fin de la ligne, comme dans Ada , Eiffel , Haskell , SQL et VHDL . Les chaînes et les commentaires sur plusieurs lignes sont marqués par des crochets doubles.
-- Commentaire sur une seule ligne --[[ Commentaire sur plusieurs lignes --]]
fonction factorielle ( n ) locale x = 1 pour i = 2 , n faire x = x * i fin retourner x fin
Contrôle du flux
Lua possède un type de test conditionnel : if then endavec des constructions facultatives elseet elseif thende contrôle d'exécution.
L'énoncé générique if then endrequiert les trois mots-clés :
si condition alors --fin du corps de l' instruction
Le elsemot clé peut être ajouté avec un bloc d'instructions d'accompagnement pour contrôler l'exécution lorsque la ifcondition est évaluée àfalse :
si condition alors --corps de l'instruction sinon --corps de l'instruction fin
L'exécution peut également être contrôlée selon plusieurs conditions à l'aide des elseif thenmots-clés :
si condition alors --corps de l'instruction elseif condition alors --corps de l'instruction else -- facultatif --facultatif par défaut corps de l'instruction fin
Lua possède quatre types de boucles conditionnelles : la whileboucle , la repeatboucle (similaire à une do whileboucle ), la forboucle numérique et la boucle générique for.
--condition = vraitant que la condition fait --les instructions se terminentrépéter --instructions jusqu'à ce que la conditionpour i = premier , dernier , delta faire --delta peut être négatif, ce qui permet à la boucle for de compter à rebours ou à la hausse --instructions --exemple : print(i) fin
Cette forboucle générique parcourrait la table _Gen utilisant la fonction itérateur standard pairs, jusqu'à ce qu'elle renvoienil :
pour la clé , la valeur par paires ( _G ) faire print ( clé , valeur ) fin
Les boucles peuvent également être imbriquées (placées à l'intérieur d'une autre boucle).
grille locale = { { 11 , 12 , 13 }, { 21 , 22 , 23 }, { 31 , 32 , 33 } }pour y , ligne par paires ( grille ) faire pour x , valeur par paires ( ligne ) faire imprimer ( x , y , valeur ) fin fin
Fonctions
Le traitement des fonctions par Lua en tant que valeurs de première classe est illustré dans l'exemple suivant, où le comportement de la fonction d'impression est modifié :
do local oldprint = print -- Stocker la fonction d'impression actuelle comme fonction oldprint print ( s ) --[[ Redéfinir la fonction d'impression. La fonction d'impression habituelle peut toujours être utilisée via oldprint. La nouvelle n'a qu'un seul argument.]] oldprint ( s == "foo" et "bar" ou s ) end end
Tous les appels futurs à printseront désormais acheminés via la nouvelle fonction, et en raison de la portée lexicale de Lua , l'ancienne fonction d'impression ne sera accessible que par la nouvelle fonction d'impression modifiée.
Lua prend également en charge les fermetures , comme illustré ci-dessous :
function addto ( x ) -- Renvoie une nouvelle fonction qui ajoute x à l'argument return function ( y ) --[[ Lorsque nous faisons référence à la variable x, qui est en dehors de la portée actuelle et dont la durée de vie serait plus courte que celle de cette fonction anonyme, Lua crée une fermeture.]] return x + y end end fourplus = addto ( 4 ) print ( fourplus ( 3 )) -- Imprime 7--Cela peut également être réalisé en appelant la fonction de la manière suivante : print ( addto ( 4 )( 3 )) --[[ Cela est dû au fait que nous appelons directement la fonction renvoyée par 'addto(4)' avec l'argument '3'. Cela permet également de réduire le coût des données et d'améliorer les performances en cas d'appel itératif.]]
Une nouvelle fermeture pour la variable xest créée à chaque addtoappel, de sorte que chaque nouvelle fonction anonyme renvoyée accèdera toujours à son propre xparamètre. La fermeture est gérée par le garbage collector de Lua, comme tout autre objet.
Une table est un ensemble de paires clé/données, où les données sont référencées par clé ; en d'autres termes, il s'agit d'un tableau associatif hétérogène haché .
Les tables sont créées à l'aide de la {}syntaxe du constructeur.
a_table = {} -- Crée une nouvelle table vide
Les tables sont toujours passées par référence (voir Appel par partage ).
Une clé (index) peut être n'importe quelle valeur sauf nilet NaN , y compris des fonctions.
a_table = { x = 10 } -- Crée une nouvelle table, avec une entrée mappant "x" au nombre 10. print ( a_table [ "x" ]) -- Imprime la valeur associée à la clé de chaîne, dans ce cas 10. b_table = a_table b_table [ "x" ] = 20 -- La valeur dans la table a été modifiée en 20. print ( b_table [ "x" ]) -- Imprime 20. print ( a_table [ "x" ]) -- Imprime également 20, car a_table et b_table font tous deux référence à la même table.
Une table est souvent utilisée comme structure (ou enregistrement ) en utilisant des chaînes comme clés. Comme cette utilisation est très courante, Lua propose une syntaxe spéciale pour accéder à ces champs.
point = { x = 10 , y = 20 } -- Créer un nouveau tableau print ( point [ "x" ]) -- Imprime 10 print ( point . x ) -- A exactement la même signification que la ligne ci-dessus. La notation par points, plus facile à lire, n'est que du sucre syntaxique.
En utilisant une table pour stocker des fonctions associées, elle peut agir comme un espace de noms.
Point = {}Point . new = fonction ( x , y ) retour { x = x , y = y } -- retour {["x"] = x, ["y"] = y} finPoint . set_x = fonction ( point , x ) point . x = x -- point["x"] = x; fin
Les tables se voient automatiquement attribuer une clé numérique, ce qui leur permet d'être utilisées comme type de données de tableau . Le premier index automatique est 1 plutôt que 0 comme c'est le cas pour de nombreux autres langages de programmation (bien qu'un index explicite de 0 soit autorisé).
Une clé numérique 1est distincte d'une clé de chaîne "1".
array = { "a" , "b" , "c" , "d" } -- Les indices sont attribués automatiquement. print ( array [ 2 ]) -- Imprime "b". L'indexation automatique dans Lua commence à 1. print ( # array ) -- Imprime 4. # est l'opérateur de longueur pour les tables et les chaînes. array [ 0 ] = "z" -- Zéro est un index légal. print ( # array ) -- Imprime toujours 4, car les tableaux Lua sont basés sur 1.
La longueur d'un tableau test définie comme étant tout indice entier ntel que t[n]n'est pas nilet t[n+1]est nil; de plus, si t[1]est nil, npeut être zéro. Pour un tableau régulier, avec des valeurs non nulles de 1 à un donné n, sa longueur est exactement celle n, l'indice de sa dernière valeur. Si le tableau a des « trous » (c'est-à-dire des valeurs nulles entre d'autres valeurs non nulles), alors #tpeut être n'importe lequel des indices qui précèdent directement une nilvaleur (c'est-à-dire qu'il peut considérer toute valeur nulle comme la fin du tableau).
fonction Point ( x , y ) -- constructeur d'objet "Point" return { x = x , y = y } -- Crée et renvoie un nouvel objet (tableau) end array = { Point ( 10 , 20 ), Point ( 30 , 40 ), Point ( 50 , 60 ) } -- Crée un tableau de points -- array = { { x = 10, y = 20 }, { x = 30, y = 40 }, { x = 50, y = 60 } }; print ( array [ 2 ]. y ) -- Imprime 40
L'utilisation d'une table de hachage pour émuler un tableau est normalement plus lente que l'utilisation d'un tableau réel ; cependant, les tables Lua sont optimisées pour être utilisées comme tableaux afin d'éviter ce problème.
Métatables
La sémantique extensible est une fonctionnalité clé de Lua, et le concept de métatable permet une personnalisation puissante des tables. L'exemple suivant illustre une table « infinie ». Pour tout n, fibs[n]donnera le n-ième nombre de Fibonacci en utilisant la programmation dynamique et la mémorisation .
fibs = { 1 , 1 } -- Valeurs initiales pour fibs[1] et fibs[2]. setmetatable ( fibs , { __index = function ( values , n ) --[[__index est une fonction prédéfinie par Lua, elle est appelée si la clé "n" n'existe pas.]] valeurs [ n ] = valeurs [ n - 1 ] + valeurs [ n - 2 ] -- Calculer et mémoriser fibs[n]. retourner valeurs [ n ] fin })
Programmation orientée objet
Bien que Lua ne dispose pas d'un concept intégré de classes , la programmation orientée objet peut être émulée à l'aide de fonctions et de tables. Un objet est formé en plaçant des méthodes et des champs dans une table. L'héritage (simple ou multiple) peut être implémenté avec des métatables, en déléguant des méthodes et des champs inexistants à un objet parent.
Il n'existe pas de concept de « classe » avec ces techniques ; on utilise plutôt des prototypes , à l'instar de Self ou de JavaScript . Les nouveaux objets sont créés soit avec une méthode factory (qui construit de nouveaux objets à partir de zéro), soit en clonant un objet existant.
Vecteur local = {} Vecteur localMeta = { __index = Vecteur }fonction Vector . new ( x , y , z ) -- Le constructeur renvoie setmetatable ({ x = x , y = y , z = z }, VectorMeta ) finfonction Vector . magnitude ( self ) -- Une autre méthode renvoie math.sqrt ( self . x ^ 2 + self . y ^ 2 + self . z ^ 2 ) finlocal vec = Vector . new ( 0 , 1 , 0 ) -- Créer un vecteur print ( vec . magnitude ( vec )) -- Appeler une méthode (sortie : 1) print ( vec . x ) -- Accéder à une variable membre (sortie : 0)
Ici, setmetatableindique à Lua de rechercher un élément dans la Vectortable s'il n'est pas présent dans la vectable. , qui est équivalent à , recherche d'abord l' élément dans la table. La table n'a pas d' élément, mais sa métatable délègue à la table l' élément lorsqu'il n'est pas trouvé dans la table. vec.magnitudevec["magnitude"]vecmagnitudevecmagnitudeVectormagnitudevec
Lua fournit un peu de sucre syntaxique pour faciliter l'orientation objet. Pour déclarer des fonctions membres dans une table prototype, on peut utiliser , qui est équivalent à . L'appel de méthodes de classe utilise également les deux points : est équivalent à . functiontable:func(args)functiontable.func(self,args)object:func(args)object.func(object,args)
Ceci étant dit, voici une classe correspondante avec :du sucre syntaxique :
vecteur local = {} vecteur . __index = vecteurfonction Vector : new ( x , y , z ) -- Le constructeur -- Puisque la définition de fonction utilise un deux-points, -- son premier argument est "self" qui fait référence -- à "Vector" return setmetatable ({ x = x , y = y , z = z }, self ) endfonction Vector : magnitude () -- Une autre méthode -- Référencer l'objet implicite en utilisant self return math.sqrt ( self.x ^ 2 + self.y ^ 2 + self.z ^ 2 ) endlocal vec = Vector : new ( 0 , 1 , 0 ) -- Créer un vecteur print ( vec : magnitude ()) -- Appeler une méthode (sortie : 1) print ( vec . x ) -- Accéder à une variable membre (sortie : 0)
Héritage
Lua prend en charge l'utilisation de métatables pour donner l'héritage de classe Lua. Dans cet exemple, nous permettons aux vecteurs d'avoir leurs valeurs multipliées par une constante dans une classe dérivée.
vecteur local = {} vecteur . __index = vecteurfonction Vector : new ( x , y , z ) -- Le constructeur -- Ici, self fait référence à la "nouvelle" -- méthode de la classe que nous appelons. Dans une classe dérivée, self sera la classe dérivée ; dans la classe Vector, self sera Vector return setmetatable ({ x = x , y = y , z = z }, self ) endfonction Vector : magnitude () -- Une autre méthode -- Référencer l'objet implicite en utilisant self return math.sqrt ( self.x ^ 2 + self.y ^ 2 + self.z ^ 2 ) end-- Exemple d'héritage de classe local VectorMult = {} VectorMult . __index = VectorMult setmetatable ( VectorMult , Vector ) -- Faire de VectorMult un enfant de Vectorfonction VectorMult : multiplier ( valeur ) self . x = self . x * valeur self . y = self . y * valeur self . z = self . z * valeur return self finlocal vec = VectorMult : new ( 0 , 1 , 0 ) -- Créer un vecteur print ( vec : magnitude ()) -- Appeler une méthode (sortie : 1) print ( vec . y ) -- Accéder à une variable membre (sortie : 1) vec : multiplier ( 2 ) -- Multiplier tous les composants du vecteur par 2 print ( vec . y ) -- Accéder à nouveau au membre (sortie : 2)
Lua prend également en charge l'héritage multiple ; __indexil peut s'agir soit d'une fonction, soit d'une table. La surcharge d'opérateur peut également être effectuée ; les métatables Lua peuvent contenir des éléments tels que __add, __subetc.
Mise en œuvre
Les programmes Lua ne sont pas interprétés directement à partir du fichier Lua textuel, mais sont compilés en bytecode, qui est ensuite exécuté sur la machine virtuelle Lua (VM). Le processus de compilation est généralement invisible pour l'utilisateur et est effectué pendant l'exécution , en particulier lorsqu'un compilateur de compilation juste-à-temps (JIT) est utilisé, mais il peut être effectué hors ligne pour augmenter les performances de chargement ou réduire l'empreinte mémoire de l'environnement hôte en laissant de côté le compilateur. Le bytecode Lua peut également être produit et exécuté à partir de Lua, en utilisant la dumpfonction de la bibliothèque de chaînes et les load/loadstring/loadfilefonctions. La version 5.3.4 de Lua est implémentée en environ 24 000 lignes de code C.
Comme la plupart des processeurs et contrairement à la plupart des machines virtuelles (qui sont basées sur la pile ), la machine virtuelle Lua est basée sur les registres et ressemble donc davantage à la plupart des conceptions matérielles. L'architecture des registres évite à la fois la copie excessive de valeurs et réduit le nombre total d'instructions par fonction. La machine virtuelle de Lua 5 est l'une des premières machines virtuelles purement basées sur les registres à avoir une large utilisation. Parrot et Dalvik d' Android sont deux autres machines virtuelles basées sur les registres bien connues. La machine virtuelle de PCScheme était également basée sur les registres.
Cet exemple est la liste du bytecode de la fonction factorielle définie ci-dessus (comme indiqué par le luaccompilateur 5.1) :
Lua est destiné à être intégré dans d'autres applications et fournit une API C à cet effet. L'API est divisée en deux parties : le noyau Lua et la bibliothèque auxiliaire Lua. La conception de l'API Lua élimine le besoin de comptage manuel des références (gestion) dans le code C, contrairement à l'API de Python . L'API, comme le langage, est minimaliste. Des fonctions avancées sont fournies par la bibliothèque auxiliaire, qui se compose en grande partie de macros de préprocesseur qui aident aux opérations de table complexes.
L'API C de Lua est basée sur une pile . Lua fournit des fonctions pour pousser et extraire la plupart des types de données C simples (entiers, flottants, etc.) vers et depuis la pile, et des fonctions pour manipuler des tables via la pile. La pile Lua est quelque peu différente d'une pile traditionnelle ; la pile peut être indexée directement, par exemple. Les indices négatifs indiquent les décalages par rapport au haut de la pile. Par exemple, −1 est le haut (la valeur la plus récemment poussée), tandis que les indices positifs indiquent les décalages par rapport au bas (la valeur la plus ancienne). Le regroupement des données entre les fonctions C et Lua s'effectue également à l'aide de la pile. Pour appeler une fonction Lua, les arguments sont poussés sur la pile, puis le lua_callest utilisé pour appeler la fonction réelle. Lors de l'écriture d'une fonction C à appeler directement depuis Lua, les arguments sont lus à partir de la pile.
Voici un exemple d'appel d'une fonction Lua depuis C :
#include <stdio.h> #include <lua.h> // Bibliothèque principale Lua (lua_*) #include <lauxlib.h> // Bibliothèque auxiliaire Lua (luaL_*)int main ( void ) { // créer un état Lua lua_State * L = luaL_newstate ();// charger et exécuter une chaîne if ( luaL_dostring ( L , "function foo (x,y) return x+y end" )) { lua_close ( L ); return -1 ; }// pousser la valeur de "foo" global (la fonction définie ci-dessus) // vers la pile, suivie des entiers 5 et 3 lua_getglobal ( L , "foo" ); lua_pushinteger ( L , 5 ); lua_pushinteger ( L , 3 ); lua_call ( L , 2 , 1 ); // appeler une fonction avec deux arguments et une valeur de retour printf ( "Résultat : %d " , lua_tointeger ( L , -1 )); // afficher la valeur entière de l'élément en haut de la pile lua_pop ( L , 1 ); // ramener la pile à son état d'origine lua_close ( L ); // fermer l'état Lua return 0 ; }
L'exécution de cet exemple donne :
$ cc -o exemple example.c -llua $ ./exemple Résultat : 8
L'API C fournit également des tables spéciales, situées à divers « pseudo-indices » dans la pile Lua. Avant LUA_GLOBALSINDEXLua 5.2 se trouve la table globale, _Gà partir de Lua, qui est l' espace de noms principal . Il existe également un registre situé à LUA_REGISTRYINDEXl'emplacement où les programmes C peuvent stocker les valeurs Lua pour une récupération ultérieure.
Modules
Outre les modules de la bibliothèque standard (core), il est possible d'écrire des extensions en utilisant l'API Lua. Les modules d'extension sont des objets partagés qui peuvent être utilisés pour étendre les fonctions de l'interpréteur en fournissant des fonctionnalités natives aux scripts Lua. Les scripts Lua peuvent charger des modules d'extension en utilisant require, tout comme les modules écrits en Lua lui-même, ou avec package.loadlib. Lorsqu'une bibliothèque C est chargée via Lua, il recherchera la fonction et l'appellera, ce qui agit comme n'importe quelle fonction C appelable depuis Lua et renvoie généralement une table remplie de méthodes. Un ensemble croissant de modules appelés rocks sont disponibles via un système de gestion de paquets nommé LuaRocks , dans l'esprit de CPAN , RubyGems et Python eggs. Des liaisons Lua pré-écrites existent pour la plupart des langages de programmation populaires, y compris d'autres langages de script. Pour C++, il existe un certain nombre d'approches basées sur des modèles et des générateurs de liaisons automatiques. require('foo')luaopen_foo
En 2003, un sondage mené par GameDev.net a montré que Lua était le langage de script le plus populaire pour la programmation de jeux. Le 12 janvier 2012, Lua a été annoncé comme lauréat du Front Line Award 2011 du magazine Game Developer dans la catégorie Outils de programmation.
Grâce à l'extension Scribunto, Lua est disponible comme langage de script côté serveur dans le logiciel MediaWiki qui gère Wikipédia et d'autres wikis. Parmi ses utilisations, on peut citer l'intégration de données de Wikidata dans des articles, et l'alimentation du système automatisé de taxobox .