Déclaration des variables et question de la portée pour Lua
Question
Je suis responsable de développement pour Bitfighter . Nous utilisons Lua comme langage de script pour permettre aux joueurs de programmer leurs propres vaisseaux robotisés.
Dans Lua, il n'est pas nécessaire de déclarer les variables. Toutes les variables par défaut ont une portée globale, sauf indication contraire. Cela conduit à des problèmes. Prenez l'extrait suivant, par exemple:
loc = bot:getLoc()
items = bot:findItems(ShipType) -- Find a Ship
minDist = 999999
found = false
for indx, item in ipairs(items) do
local d = loc:distSquared(item:getLoc())
if(d < minDist) then
closestItem = item
minDist = d
end
end
if(closestItem != nil) then
firingAngle = getFiringSolution(closestItem)
end
Dans cet extrait, si findItems () ne renvoie aucun candidat, mostItem fera toujours référence au navire trouvé la dernière fois et, dans l'intervalle, ce navire aurait pu être tué. Si le navire est tué, il n'existe plus et getFiringSolution () échouera.
Avez-vous repéré le problème? Eh bien, mes utilisateurs non plus. C'est subtil, mais avec un effet dramatique.
Une solution consisterait à exiger que toutes les variables soient déclarées et que toutes les variables soient définies par défaut sur la portée locale. Ce changement n'empêchera pas les programmeurs de se référer à des objets qui n'existent plus, mais il leur sera plus difficile de le faire par inadvertance.
Existe-t-il un moyen de dire à Lua de mettre tous les vars par défaut en portée locale et / ou d’exiger qu’ils soient déclarés? Je connais d'autres langues (Perl, par exemple) proposant cette option.
Merci!
Beaucoup de bonnes réponses ici, merci!
J'ai décidé de choisir une version légèrement modifiée du module Lua 'strict'. Cela semble me mener là où je veux aller, et je vais le bidouiller un peu pour améliorer les messages et les rendre plus appropriés à mon contexte particulier.
La solution
Il n'y a pas d'option pour définir ce comportement, mais il existe un module 'strict' fourni avec l'installation standard, qui fait exactement cela (en modifiant les méta-tables). Usage: nécessite 'strict'
Pour plus d'informations et d'autres solutions plus détaillées: http://lua-users.org/wiki / DetectingUndefinedVariables , mais je recommande 'strict'.
Autres conseils
Sorta.
À Lua, les globaux résident théoriquement dans la table de globals _G
(la réalité est un peu plus complexe, mais du côté de Lua, il n’ya aucun moyen de le savoir). Comme avec toutes les autres tables Lua, il est possible d'associer un __ newindex
à _G
qui contrôle la manière dont les variables y sont ajoutées. Laissez ce gestionnaire __ newindex
faire ce que vous voulez quand quelqu'un crée un global: émet une erreur, l'autorise mais affiche un avertissement, etc.
Pour manipuler _G
, il est plus simple et plus simple d'utiliser setfenv
. Voir la documentation .
"Local par défaut est incorrect ". S'il vous plaît voir
http://lua-users.org/wiki/LocalByDefault
http://lua-users.org/wiki/LuaScopingDiscussion
Vous devez utiliser une sorte de protection de l'environnement mondial. Il existe certains outils statiques pour le faire (pas trop évolués), mais la solution la plus courante consiste à utiliser une protection à l'exécution, basée sur __ index
et __ newindex
dans _G
est métatable.
Shameles plug: cette page peut également être utile:
http://code.google.com/p/lua-alchemy / wiki / LuaGlobalEnvironmentProtection
Notez que, bien qu’il traite de Lua intégré à swf, la technique décrite (voir sources ) fonctionne pour le générique Lua. Nous utilisons quelque chose dans ce sens dans notre code de production au travail.
En fait, la variable globale supplémentaire avec la référence obsolète au navire sera suffisante pour empêcher le CPG d’écarter l’objet. Donc, il pourrait être détecté au moment de l'exécution en remarquant que le navire est maintenant "mort". et refusant de faire quoi que ce soit avec elle. Ce n'est toujours pas le bon navire, mais au moins vous ne vous écrasez pas.
Vous pouvez notamment conserver les scripts utilisateur dans un bac à sable , probablement un bac à sable par script. Avec la bonne manipulation de la table d'environnement du sandbox ou de son métatable, vous pouvez supprimer toutes les variables globales ou la plupart des variables globales du sandbox avant (ou juste après) l'appel du code de l'utilisateur.
Nettoyer le bac à sable après les appels aurait l’avantage de supprimer des références supplémentaires à des choses qui ne devraient pas traîner. Cela peut être fait en conservant une liste blanche des champs autorisés à rester dans l'environnement et en supprimant tout le reste.
Par exemple, ce qui suit implémente un appel sandbox dans une fonction fournie par l'utilisateur avec un environnement contenant uniquement les noms en liste blanche derrière une nouvelle table de travail fournie pour chaque appel.
-- table of globals that will available to user scripts local user_G = { print=_G.print, math=_G.math, -- ... } -- metatable for user sandbox local env_mt = { __index=user_G } -- call the function in a sandbox with an environment in which new global -- variables can be created and modified but they will be discarded when the -- user code completes. function doUserCode(user_code, ...) local env = setmetatable({}, env_mt) -- create a fresh user environment with RO globals setfenv(user_code, env) -- hang it on the user code local results = {pcall(user_code, ...)} setfenv(user_code,{}) return unpack(results) end
Cela pourrait être étendu pour rendre la table globale en lecture seule en la repoussant derrière un autre accès métatable si vous le souhaitez.
Notez qu'une solution complète de bac à sable considérerait également ce qu'il faut faire avec un code utilisateur qui exécute accidentellement (ou malicieusement) une boucle infinie (ou simplement très longue) ou une autre opération. Les solutions générales à cet égard sont un sujet de discussion occasionnel sur la liste Lua , mais de bonnes solutions sont difficiles.