[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Lua addition proposal: Userdata as function environment
- From: "Jerome Vuarand" <jerome.vuarand@...>
- Date: Wed, 4 Oct 2006 16:02:55 -0400
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.