Использование окружения

Довольно часто в Lua возникает задача загрузить из файла какую-либо таблицу с конфигурационными данными. При этом часто возникает дилемма, как лучше оформить таблицу внутри файла. Я вижу два варианта. Первый простой - в файле options.lua пишем:

  local options = 
{
visible = true,
size = {640, 480},
}

return options

Тогда код, выполняющий загрузку опций выглядит просто:

  local options = dofile("options.lua")

Таким образом, в глобальном пространстве имен не появляется никаких новых имен, однако файл с опциями содержит лишние слова local и return, что некоторым разработчикам может не понравиться из эстетических соображений. Для таких эстетов есть второй вариант, посложнее. Файл options.lua теперь выглядит так:

  options = 
{
visible = true,
size = {640, 480},
}

Если мы теперь вызовем:

  dofile("options.lua")

то в глобальном пространстве имен появится таблица options, которая может затереть существующую таблицу options, и найти причину бага, связанную с этой заменой, может быть очень непросто. Для этого нам пригодится такая штука в Lua, как "окружение". Окружение - это таблица, которая будет являться глобальным пространством имен для вызываемой функции. Устанавливается окружение функцией setfenv(f, table). То есть, все "глобальные" переменные, создаваемые внутри функции на самом деле станут полями таблицы, установленной в качестве окружения. Например, у нас есть функция, создающая глобальную таблицу globalTable:

  function fun()
globalTable = {а = 15}
end

Если мы вызовем эту функцию, то в глобальном простанстве имен появится таблица globalTable:

  fun()
print(globalTable.a) --> 15

Теперь установим для этой функции окружение:

  function fun()
globalTable = {а = 15}
end

local env = {}

setfenv(fun, env)
fun()
print(globalTable.a) --> nil
print(env.globalTable.a) --> 15

Теперь, владея этими сакральными знаниями, можно загрузить опции из модифицированного файла options.lua не в глобальное пространство имен:

  local f = loadfile(filename)

Функция loadfile() возвращает функцию, которая загрузит данные из файла. Чтобы данные из этого файла не попали в глобальное пространство имен, установим для функции f окружение:

  local env = {}

setfenv(f, env)
f()

Теперь локальная таблица env содержит таблицу options, загруженную из файла options.lua.

Теперь все тоже самое, только для С. Для файла options.lua вида:

  local options = 
{
visible = true,
size = {640, 480},
}

return options

все по прежнему просто:

  // stack:
luaL_dofile(L, "options.lua"); // stack: options

Для файла options.lua вида:

  options = 
{
visible = true,
size = {640, 480},
}

код будет немного длиннее:

  // stack:
// загружаем таблицу для окружения
// создаем новую, как в примере ниже,
// либо на вершину стека помещаем существующую таблицу вызовами
// lua_getglobal() или lua_pushvalue(L, индекс_таблицы_в_стеке))
lua_newtable(L); // stack: env
lua_pushvalue(L, -1); // stack: env env
luaL_loadfile(L, "options.lua"); // stak: env env loadFunсtion
lua_setfenv(L, -2); // env loadFunсtion
if(lua_pcall(L, 0, 0, 0))
{
// случилось страшное!!!
}

// stack: env
lua_pushstring(L, "options"); // stack: env "options"
lua_gettable(L, -2); // stack: env env.options
// теперь таблица options из файла находится на вершине стека
// работаем с таблицей options ...
// больше таблица options не нужна, выталкиваем ее
lua_pop(L, 1); // stack: env
// таблица env тоже больше не нужна, выталкиваем ее
lua_pop(L, 1); // stack:

Стек в исходном состоянии.

Для написания этой статьи использовался инструмент markdown.
Статья в формате .markdown здесь.

Hosted by uCoz