Bound Scalar Globals One |
|
C
function which manipulates an internal object. This is easy in Lua 4 but Lua 5 lacks most of the metamethods which would be used for a naive Lua 4 implementation.
Here is one solution, which requires that the globals be bound by name. See BoundScalarGlobalsTwo for a different implementation.
The idea here is simple: the bound scalars are not present in the globals table; instead, getter and setter functions are placed into two other tables, and these functions are called by the global table's __index
and __newindex
metamethods. This has no performance impact on global variables which have values, and hopefully reasonably limited impact on bound variables. (Proxied would be a better name, probably -- the phrase "bound scalar" comes from Perl. [1])
Here, I take care to use any existing __index
and __newindex
metamethods from the existing globals table. Hopefully, other existing metamethods don't matter, but they could be handled too.
-- BindScalar1 -- Version 0.2 -- RiciLake -- -- Change history -- -------------- -- Fixed the case where the globals table didn't have a metatable -- Corrected the behaviour on set where there was no existing -- __newindex metamethod so that it now rawsets the table -- Check to see if the old __index method is not a function -- to mimic the default behaviour -- Wrote a couple of quick example getters and setters -- Actually made sure it compiles and runs -- -- TODO -- ---- -- Actually debug it with real metatables -- Think of a setter that lets you set something -- -- BUGS -- ---- -- If you specify a getter and don't specify a setter, the binding stops -- working. It should be necessary to specify both. -- -- The API needs to be improved do local meta, getters, setters = {}, {}, {} local old_meta = getmetatable(getfenv()) local old_index, old_newindex if old_meta then old_index, old_newindex = old_meta.__index, old_meta.__newindex end -- at this point you have to populate the getters and setters table -- somehow, probably by getting them from your C code. -- Here is an example without C: -- the getter receives the name of the global as an argument local function get_time(k) if k == "gmt" then return os.date("!%c") else return os.date("%c") end end -- the setter actually receives the name and the proposed value -- but in this example we don't need them. local function set_time() error "You cannot change the time" end -- now put them into getters and setters. There should probably -- be a function to do that, something like: -- bind_scalar("now", get_time, set_time) getters.now = get_time getters.gmt = get_time setters.now = set_time setters.gmt = set_time -- Another example. Particular environment variables are made -- into globals. (Change this to USERNAME for Windows NT.) local function get_env(k) return os.getenv(k) end local function set_env(k, v) if os.setenv then os.setenv(k, v) else error "You cannot change environment variables on this platform." end end getters.USER = get_env setters.USER = set_env -- hmm? it's just an example -- It might be nice to change the calls below to object calls, -- such as getters[k](getters[k], k) -- For efficiency, you probably only want to do that lookup once. -- Here is the actual implementation of the metamethods. meta = {} if type(old_index) == "function" then function meta.__index(t, k) if getters[k] then return getters[k](k) else return old_index(t, k) end end elseif type(old_index) == "nil" then function meta.__index(t, k) if getters[k] then return getters[k](k) end end else function meta.__index(t, k) if getters[k] then return getters[k](k) else return old_index[k] end end end if old_newindex then function meta.__newindex(t, k, v) if setters[k] then setters[k](k, v) else old_newindex(t, k, v) end end else function meta.__newindex(t, k, v) if setters[k] then setters[k](k, v) else rawset(t, k, v) end end end setmetatable(getfenv(), meta) end
Sample output:
-- now is deferred to a function. > print(now) Thu Jan 16 12:34:40 2003 -- so is gmt > print(gmt) Thu Jan 16 17:35:34 2003 -- setting "works"; the variable is read-only > now = "tomorrow" glue.lua:27: You cannot change the time stack traceback: [C]: in function `error' glue.lua:27: in function `?' glue.lua:91: in function <glue.lua:88> stdin:1: in main chunk [C]:[C] -- This mechanism might be useful in a CGI script, for example > print(USER) rlake -- Most platforms implement setenv but it's not ANSI standard. This -- would work if you patched the os library. > USER="root" glue.lua:44: You cannot change environment variables on this platform. stack traceback: [C]: in function `error' glue.lua:44: in function `?' glue.lua:91: in function <glue.lua:88> stdin:1: in main chunk [C]:[C] -- Ordinary globals continue to be ordinary. > print(j) nil > j = 7 > print(j) 7
Footnotes:
This has only one problem: __newindex is only called on values that were not set before, e.g. x = "bla"; x = "more bla" will call __newindex only once. --Anon
Yes, that's why it doesn't actually set the value if it users the setter function. However, when you create the setter functions, you must first ensure that the corresponding global is not set; if you want to add a setter function later on, the same thing applies. Give it a try :) --RiciLake