lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


While trying to answer some recent mails on the list I did some
experiment with environments and noticed that it is not possible to set
anything else than a table as a function environment.
 
I think it would be nice to be able to set any value as the environment,
especially a userdata. The value in question should have __index and
__newindex metamethods. If it don't variable reads would return nil and
variable writes would be ignored.
 
An example of use would be a configuration mechanism similar to one that
has been discussed recently. Here a C++/Lua hypothetical example:

-- Lua
-----------------------------------------------------------------------
local mylib = require("mylib")

local configobject = mylib.getconfig() -- returns a userdata

local configscript = loadfile("config.lua")

setfenv(configscript, configobject) -- Here is the impossible part in
current Lua

configscript()
-- Lua
-----------------------------------------------------------------------

// C++
///////////////////////////////////////////////////////////////////////
// A simple tree class, with double and string values on each node
class Object
{
private:
    map<string, Object*> childs;
    map<string, double> numbers;
    map<string, string> strings;
public:
    void setvar(string name, double value) { numbers[name] = value; }
    void setvar(string name, string value) { strings[name] = value; }
    Object* getchild(string name) { return childs[name]; }
}

static int pushobject(lua_State* L, Object* object);

// Sets a field in the object, will be called by __newindex
// Arg1 is the config object
// Arg2 is string key
// Arg3 is value, numbers and strings accepted, the rest ignored
static int config_setvar(lua_State* L)
{
    Object** pconfig = lua_touserdata(L, 1);
    if (lua_isnumber(L, 3))
    {
        (*pconfig)->setvar(lua_tostring(L, 2), lua_tonumber(L, 3));
    }
    else if (lua_isstring(L, 3))
    {
        (*pconfig)->setvar(lua_tostring(L, 2), lua_tostring(L, 3));
    }
    return 0;
}

// Get a child, will be called by __index
static int config_getchild(lua_State* L)
{
    Object** pconfig = lua_touserdata(L, 1);
    Object* child = (*pconfig)->getchild(lua_tostring(L, 2));
    if (child)
        return pushobject(L, child);
    else
        return 0;
}

// Puts an object on stack and return 1
static int pushobject(lua_State* L, Object* object)
{
    // Put object address as a userdata
    Object** pobject = (Object**)lua_newuserdata(L, sizeof(Object*));
    *pobject = object;
    // Set a metatable to forward table-like access to object methods
    lua_newtable(L);
    lua_pushcfunction(L, config_setvar);
    lua_setfield(L, -2, "__newindex");
    lua_pushcfunction(L, config_getchild);
    lua_setfield(L, -2, "__index");
    lua_setmetatable(L, -2);
    return 1;
}

// Assume global_config is a valid object tree
extern Object* global_config;

extern "C" int mylib_getconfig(lua_State* L)
{
    return pushobject(L, global_config);
}

/* Standard module declaration should go there, exposing mylib_getconfig
*/
// C++
///////////////////////////////////////////////////////////////////////

I hope the example is clear enough and shows a good use case where
userdata environment would be useful. The same effect can be achieved
with a temporary table, but in complex cases (more complex than my
example), it could be troublesome.