lua-users home
lua-l archive

Lua addition proposal: Userdata as function environment

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

AltStyle によって変換されたページ (->オリジナル) /