2009-06-18 18 views
5

Soy desarrollador principal para Bitfighter, y estamos usando Lua como lenguaje de scripting para permitir a los jugadores programar sus propias naves de robot personalizadas.Variables de declaración y pregunta de ámbito para Lua

En Lua, no necesita declarar variables, y todas las variables tienen por defecto el alcance global, a menos que se indique lo contrario. Esto lleva a algunos problemas. Tome el siguiente fragmento, por ejemplo:

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 

En este fragmento, si findItems() devuelve ningún candidato, entonces closestItem habrá todavía se refieren a cualquier buque con el que se encontró la última vez, y en el tiempo intermedio, ese barco podría haber sido asesinado. Si el barco muere, ya no existe y getFiringSolution() fallará.

¿Has detectado el problema? Bueno, tampoco lo harán mis usuarios. Es sutil, pero con un efecto dramático.

Una solución sería exigir que se declaren todas las variables y que todas las variables se definan por defecto en el ámbito local. Si bien ese cambio no haría imposible que los programadores se refirieran a objetos que ya no existen, sería más difícil hacerlo inadvertidamente.

¿Hay alguna manera de decirle a Lua que de forma predeterminada todos los vars alcancen el alcance local, y/o que exijan que se declaren? Sé que algunos otros idiomas (por ejemplo, Perl) tienen esta opción disponible.

Gracias!


¡Muchas buenas respuestas aquí, gracias!

He decidido ir con una versión ligeramente modificada del módulo Lua 'strict'. Esto parece llevarme a donde quiero ir, y lo hackearé un poco para mejorar los mensajes y hacerlos más apropiados para mi contexto particular.

+0

Por cierto, ¿tiene alguna buena razón para poner llaves adicionales dentro de sus declaraciones if? –

+0

Bueno, antes de hacer la pregunta, habría respondido que se requería. ¡Pero ahora tengo que responder que omitir los parens me parece mal! – Watusimoto

+2

Es una preferencia personal, por supuesto. Pero es importante recordar que Lua no es C o Pascal o lo que sea. Lua es Lua y para usarlo efectivamente debes usarlo tal como es, no como si fuera un sustituto pobre para otro lenguaje de programación. Descubrí que esa "sintaxis parece incorrecta" ayuda a poner a Lua en una parte de mi cerebro distinta de C++ y otros idiomas que conozco. En resumen, mi opinión es: si está escrito en Lua, ¡escríbelo a la manera de Lua! :-) –

Respuesta

3

No hay opción para establecer este comportamiento, pero hay un módulo 'estricto' provisto con la instalación estándar, que hace exactamente eso (modificando las meta-tablas). Uso: requieren 'estricto'

Para obtener información más detallada y otras soluciones: http://lua-users.org/wiki/DetectingUndefinedVariables, pero recomiendo 'estricta'.

+0

Esta parece ser la respuesta más simple, y es lo que estaba buscando.Me gustaría entender mejor las otras soluciones sugeridas aquí para ver si hay algún inconveniente al usar 'estricto'. – Watusimoto

2

Sorta.

En Lua, los globals definitivamente viven en la tabla global _G (la realidad es un poco más compleja, pero desde el lado de Lua no hay manera de decirle a AFAIK). Como con todas las demás tablas Lua, es posible adjuntar un metatable __newindex a _G que controla cómo se le agregan las variables. Deje que este controlador __newindex hacer lo que quiere hacer cuando alguien crea un mundial: lanzar un error, pero que permite imprimir una advertencia, etc.

entrometerse en _G, es más simple y más limpio para usar setfenv. Vea el documentation.

0

En realidad, la variable global extra con la referencia obsoleta al barco será suficiente para evitar que el GC descarte el objeto. Por lo tanto, podría detectarse en tiempo de ejecución al notar que el barco ahora está "muerto" y se niega a hacer nada con él. Todavía no es el barco correcto, pero al menos no se bloquea.

Una cosa que puede hacer es mantener los scripts de usuario en un sandbox, probablemente un entorno limitado por script.Con la manipulación correcta de la tabla de entorno de la zona de pruebas o su metatabla, puede organizar desechar todas o la mayoría de las variables globales de la zona de pruebas antes (o justo después) de llamar al código del usuario.

Limpiar la caja de arena después de las llamadas tendría la ventaja de descartar referencias adicionales a cosas que no deberían quedarse. Esto podría hacerse manteniendo una lista blanca de los campos que pueden permanecer en el entorno y borrando todo el resto.

Por ejemplo, lo siguiente implementa una llamada de espacio aislado a una función suministrada por el usuario con un entorno que contiene solo nombres en la lista blanca detrás de una nueva tabla reutilizable proporcionada para cada llamada.

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

Esto puede extenderse para hacer la tabla mundial de sólo lectura empujándola detrás de un acceso más metatabla si querías.

Tenga en cuenta que una solución de espacio aislado completa también consideraría qué hacer con el código de usuario que accidentalmente (o maliciosamente) ejecuta un ciclo infinito (o simplemente muy largo) u otra operación. Las soluciones generales para esto son un tema ocasional de discusión en el Lua list, pero las buenas soluciones son difíciles.

+0

El problema con la referencia al objeto de barco muerto es que la vida del objeto está controlada por el programa maestro de C++, por lo que el objeto puede eliminarse sin el consentimiento o control de Lua. Lua tiene una referencia a un puntero userdata, que de repente apunta a un objeto no válido. ¿Cómo puedo detectar eso? Necesito tener un mejor manejo de esto, y estaría abierto a cualquier sugerencia. Estoy trabajando en la línea de una caja de arena, pero quiero permitir la creación de variables globales siempre que se creen intencionalmente y deliberadamente. Todas las sugerencias anteriores parecen lograr eso. – Watusimoto

2

"Local por defecto es incorrecto". Por favor, vea

http://lua-users.org/wiki/LocalByDefault

http://lua-users.org/wiki/LuaScopingDiscussion

Es necesario utilizar algún tipo de protección del medio ambiente global. Hay algunas herramientas estáticas para hacerlo (no demasiado maduras), pero la solución más común es utilizar protección en tiempo de ejecución, basada en __index y __newindex en el metatabla de _G.

Shameles enchufe: esta página también puede ser útil:

http://code.google.com/p/lua-alchemy/wiki/LuaGlobalEnvironmentProtection

Tenga en cuenta que mientras se discute Lua incrustado en SWF, la técnica descrita (ver sources) trabajan para Lua genérico. Usamos algo de esta manera en nuestro código de producción en el trabajo.

+1

Local está predeterminado (discutible) incorrecto, pero entonces ¡es global por defecto! Revisé el código que señalaste, y parece que hace algo muy similar al uso del módulo 'estricto' sugerido anteriormente. ¿Difieren de manera significativa? – Watusimoto

+0

Global también está mal por defecto, pero es un legado de Lua y apenas se puede cambiar (también es muy útil cuando se escriben mini-DSL en Lua). –

+0

La principal diferencia (entre esos módulos estrictos) es que el mío trata de forzar la declaración explícita y concisa de las variables globales (ya que las veo como cosas malvadas que deben eliminarse en mi código, incluidas las funciones globales). Lua's etc/strict.lua es un poco menos explícito. Pero es una preferencia personal. –

Cuestiones relacionadas