Объявление переменных и области видимости для Lua - PullRequest
5 голосов
/ 18 июня 2009

Я ведущий разработчик для Bitfighter , и мы используем Lua в качестве языка сценариев, чтобы позволить игрокам программировать свои собственные корабли роботов.

В Lua вам не нужно объявлять переменные, и все переменные по умолчанию имеют глобальную область видимости, если не указано иное. Это приводит к некоторым проблемам. Возьмите следующий фрагмент, например:

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

В этом фрагменте, если findItems () не возвращает кандидатов, тогда closestItem по-прежнему будет ссылаться на любой корабль, который был найден в последний раз, и за прошедшее время этот корабль мог быть убит. Если корабль убит, он больше не существует, и getFiringSolution () завершится ошибкой.

Вы заметили проблему? Ну, и не будут мои пользователи. Это тонкий, но с драматическим эффектом.

Одним из решений было бы требование, чтобы все переменные были объявлены, а для всех переменных по умолчанию использовалась локальная область. Хотя это изменение не лишит программистов возможности ссылаться на объекты, которые больше не существуют, это усложнит непреднамеренное выполнение этого.

Есть ли какой-нибудь способ указать Lua по умолчанию все переменные в локальной области и / или требовать, чтобы они были объявлены? Я знаю, что на некоторых других языках (например, Perl) эта опция доступна.

Спасибо!


Здесь много хороших ответов, спасибо!

Я решил пойти с немного модифицированной версией Lua «строгого» модуля. Похоже, это поможет мне понять, куда я хочу пойти, и я немного его взломаю, чтобы улучшить сообщения и сделать их более подходящими для моего конкретного контекста.

Ответы [ 4 ]

3 голосов
/ 19 июня 2009

Нет опции, чтобы установить это поведение, но есть модуль 'strict', предоставляемый со стандартной установкой, который делает именно это (путем изменения мета-таблиц). Использование: требует "строгого"

Для получения более подробной информации и других решений: http://lua -users.org / wiki / DetectingUndefinedVariables , но я рекомендую 'строгий'.

2 голосов
/ 19 июня 2009

"Локально по умолчанию неправильно ". Пожалуйста, смотрите

http://lua -users.org / вики / LocalByDefault

http://lua -users.org / вики / LuaScopingDiscussion

Вам необходимо использовать какую-то глобальную защиту окружающей среды. Для этого есть несколько статических инструментов (не слишком зрелых), но наиболее распространенным решением является использование защиты во время выполнения на основе __index и __newindex в метатабельных _G.

Плагин Shameles: эта страница также может быть полезна:

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

Обратите внимание, что, хотя в нем обсуждается Lua, встроенный в SWF, описанная техника (см. sources ) работает для универсального Lua. Мы используем что-то подобное в нашем производственном коде на работе.

2 голосов
/ 19 июня 2009

Сорт.

В Lua глобалы условно живут в таблице глобалов _G (реальность немного сложнее, но со стороны Lua невозможно сказать AFAIK). Как и во всех других таблицах Lua, можно прикрепить метатаблицу __newindex к _G, которая контролирует, как к ней добавляются переменные. Пусть этот обработчик __newindex делает все, что вы хотите, когда кто-то создает глобальный объект: выдает ошибку, разрешает ее, но печатает предупреждение и т. Д.

Вмешиваться в _G проще всего и проще setfenv. См. документацию .

0 голосов
/ 19 июня 2009

На самом деле, дополнительная глобальная переменная с устаревшей ссылкой на корабль будет достаточной, чтобы GC не отказался от объекта. Так что это можно обнаружить во время выполнения, заметив, что корабль сейчас «мертв» и отказавшись что-либо с ним делать. Это все еще не тот корабль, но, по крайней мере, вы не разбиваетесь.

Одна вещь, которую вы можете сделать, это хранить пользовательские скрипты в песочнице , возможно, в песочнице для каждого скрипта. Правильно манипулируя таблицей окружения песочницы или ее метатабельной таблицей, вы можете отказаться от всех или большинства глобальных переменных из песочницы до (или сразу после) вызова кода пользователя.

Очистка песочницы после звонков имеет преимущество в том, что отбрасывает дополнительные ссылки на вещи, которые не должны висеть вокруг. Это можно сделать, сохранив белый список полей, которым разрешено оставаться в среде, и удалив все остальные.

Например, следующее реализует изолированную программу для пользовательской функции со средой, содержащей только имена из белого списка за свежей скретч-таблицей, предоставляемой для каждого вызова.

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

Это может быть расширено, чтобы сделать глобальную таблицу доступной только для чтения, отодвинув ее за еще один доступ с метатизацией, если хотите.

Обратите внимание, что полное решение для песочницы также учитывает, что делать с пользовательским кодом, который случайно (или злонамеренно) выполняет бесконечный (или просто очень длинный) цикл или другую операцию. Общие решения для этого - случайная тема обсуждения в Lua list , но хорошие решения трудны.

...